@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,486 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
|
|
6
|
+
import { run, runCapture } from './utils/proc/proc.mjs';
|
|
7
|
+
import { withTempRoot, gitEnv, initMonorepoStub, initSplitRepoStub } from './testkit/monorepo_port_testkit.mjs';
|
|
8
|
+
|
|
9
|
+
test('monorepo port rejects when target repo is dirty', async (t) => {
|
|
10
|
+
const root = await withTempRoot(t);
|
|
11
|
+
const target = join(root, 'target-mono');
|
|
12
|
+
const sourceCli = join(root, 'source-cli');
|
|
13
|
+
const env = gitEnv();
|
|
14
|
+
|
|
15
|
+
await initMonorepoStub({ dir: target, env, seed: { 'apps/cli/hello.txt': 'v1\n' } });
|
|
16
|
+
const base = await initSplitRepoStub({ dir: sourceCli, env, name: 'cli', seed: { 'hello.txt': 'v1\n' } });
|
|
17
|
+
await writeFile(join(sourceCli, 'hello.txt'), 'v2\n', 'utf-8');
|
|
18
|
+
await run('git', ['add', '.'], { cwd: sourceCli, env });
|
|
19
|
+
await run('git', ['commit', '-q', '-m', 'feat: update hello'], { cwd: sourceCli, env });
|
|
20
|
+
|
|
21
|
+
// Make target dirty.
|
|
22
|
+
await writeFile(join(target, 'apps', 'cli', 'uncommitted.txt'), 'dirty\n', 'utf-8');
|
|
23
|
+
|
|
24
|
+
await assert.rejects(async () => {
|
|
25
|
+
await runCapture(
|
|
26
|
+
process.execPath,
|
|
27
|
+
[
|
|
28
|
+
join(process.cwd(), 'scripts', 'monorepo.mjs'),
|
|
29
|
+
'port',
|
|
30
|
+
`--target=${target}`,
|
|
31
|
+
`--branch=port/test-dirty`,
|
|
32
|
+
'--base=main',
|
|
33
|
+
`--from-happy-cli=${sourceCli}`,
|
|
34
|
+
`--from-happy-cli-base=${base}`,
|
|
35
|
+
'--json',
|
|
36
|
+
],
|
|
37
|
+
{ cwd: process.cwd(), env }
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('monorepo port rejects invalid target repo layout', async (t) => {
|
|
43
|
+
const root = await withTempRoot(t);
|
|
44
|
+
const target = join(root, 'not-a-mono');
|
|
45
|
+
const sourceCli = join(root, 'source-cli');
|
|
46
|
+
const env = gitEnv();
|
|
47
|
+
|
|
48
|
+
await mkdir(target, { recursive: true });
|
|
49
|
+
await run('git', ['init', '-q'], { cwd: target, env });
|
|
50
|
+
await run('git', ['checkout', '-q', '-b', 'main'], { cwd: target, env });
|
|
51
|
+
await writeFile(join(target, 'README.md'), 'not a monorepo\n', 'utf-8');
|
|
52
|
+
await run('git', ['add', '.'], { cwd: target, env });
|
|
53
|
+
await run('git', ['commit', '-q', '-m', 'chore: init'], { cwd: target, env });
|
|
54
|
+
|
|
55
|
+
const base = await initSplitRepoStub({ dir: sourceCli, env, name: 'cli', seed: { 'hello.txt': 'v1\n' } });
|
|
56
|
+
|
|
57
|
+
await assert.rejects(async () => {
|
|
58
|
+
await runCapture(
|
|
59
|
+
process.execPath,
|
|
60
|
+
[
|
|
61
|
+
join(process.cwd(), 'scripts', 'monorepo.mjs'),
|
|
62
|
+
'port',
|
|
63
|
+
`--target=${target}`,
|
|
64
|
+
`--branch=port/test-invalid-target`,
|
|
65
|
+
'--base=main',
|
|
66
|
+
`--from-happy-cli=${sourceCli}`,
|
|
67
|
+
`--from-happy-cli-base=${base}`,
|
|
68
|
+
],
|
|
69
|
+
{ cwd: process.cwd(), env }
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('monorepo port validates incompatible flags with --onto-current', async (t) => {
|
|
75
|
+
const root = await withTempRoot(t);
|
|
76
|
+
const target = join(root, 'target-mono');
|
|
77
|
+
const sourceCli = join(root, 'source-cli');
|
|
78
|
+
const env = gitEnv();
|
|
79
|
+
|
|
80
|
+
await initMonorepoStub({ dir: target, env, seed: { 'apps/cli/hello.txt': 'v1\n' } });
|
|
81
|
+
const base = await initSplitRepoStub({ dir: sourceCli, env, name: 'cli', seed: { 'hello.txt': 'v1\n' } });
|
|
82
|
+
|
|
83
|
+
await assert.rejects(async () => {
|
|
84
|
+
await runCapture(
|
|
85
|
+
process.execPath,
|
|
86
|
+
[
|
|
87
|
+
join(process.cwd(), 'scripts', 'monorepo.mjs'),
|
|
88
|
+
'port',
|
|
89
|
+
`--target=${target}`,
|
|
90
|
+
'--onto-current',
|
|
91
|
+
`--branch=port/nope`,
|
|
92
|
+
`--from-happy-cli=${sourceCli}`,
|
|
93
|
+
`--from-happy-cli-base=${base}`,
|
|
94
|
+
],
|
|
95
|
+
{ cwd: process.cwd(), env }
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
await assert.rejects(async () => {
|
|
100
|
+
await runCapture(
|
|
101
|
+
process.execPath,
|
|
102
|
+
[
|
|
103
|
+
join(process.cwd(), 'scripts', 'monorepo.mjs'),
|
|
104
|
+
'port',
|
|
105
|
+
`--target=${target}`,
|
|
106
|
+
'--onto-current',
|
|
107
|
+
`--base=main`,
|
|
108
|
+
`--from-happy-cli=${sourceCli}`,
|
|
109
|
+
`--from-happy-cli-base=${base}`,
|
|
110
|
+
],
|
|
111
|
+
{ cwd: process.cwd(), env }
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('monorepo port succeeds with an empty commit range (no patches)', async (t) => {
|
|
117
|
+
const root = await withTempRoot(t);
|
|
118
|
+
const target = join(root, 'target-mono');
|
|
119
|
+
const sourceCli = join(root, 'source-cli');
|
|
120
|
+
const env = gitEnv();
|
|
121
|
+
|
|
122
|
+
await initMonorepoStub({ dir: target, env, seed: { 'apps/cli/hello.txt': 'v1\n' } });
|
|
123
|
+
const base = await initSplitRepoStub({ dir: sourceCli, env, name: 'cli', seed: { 'hello.txt': 'v1\n' } });
|
|
124
|
+
|
|
125
|
+
const out = await runCapture(
|
|
126
|
+
process.execPath,
|
|
127
|
+
[
|
|
128
|
+
join(process.cwd(), 'scripts', 'monorepo.mjs'),
|
|
129
|
+
'port',
|
|
130
|
+
`--target=${target}`,
|
|
131
|
+
`--branch=port/empty-range`,
|
|
132
|
+
'--base=main',
|
|
133
|
+
`--from-happy-cli=${sourceCli}`,
|
|
134
|
+
`--from-happy-cli-base=${base}`,
|
|
135
|
+
'--json',
|
|
136
|
+
],
|
|
137
|
+
{ cwd: process.cwd(), env }
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const parsed = JSON.parse(out.trim());
|
|
141
|
+
assert.equal(parsed.ok, true);
|
|
142
|
+
assert.equal(parsed.results[0].patches, 0);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('monorepo port skips already-applied patches even without --skip-applied', async (t) => {
|
|
146
|
+
const root = await withTempRoot(t);
|
|
147
|
+
const target = join(root, 'target-mono');
|
|
148
|
+
const sourceCli = join(root, 'source-cli');
|
|
149
|
+
const env = gitEnv();
|
|
150
|
+
|
|
151
|
+
await initMonorepoStub({ dir: target, env, seed: { 'apps/cli/hello.txt': 'v2\n' } });
|
|
152
|
+
const base = await initSplitRepoStub({ dir: sourceCli, env, name: 'cli', seed: { 'hello.txt': 'v1\n' } });
|
|
153
|
+
await writeFile(join(sourceCli, 'hello.txt'), 'v2\n', 'utf-8');
|
|
154
|
+
await run('git', ['add', '.'], { cwd: sourceCli, env });
|
|
155
|
+
await run('git', ['commit', '-q', '-m', 'feat: update hello'], { cwd: sourceCli, env });
|
|
156
|
+
|
|
157
|
+
const out = await runCapture(
|
|
158
|
+
process.execPath,
|
|
159
|
+
[
|
|
160
|
+
join(process.cwd(), 'scripts', 'monorepo.mjs'),
|
|
161
|
+
'port',
|
|
162
|
+
`--target=${target}`,
|
|
163
|
+
`--branch=port/skip-applied-default`,
|
|
164
|
+
'--base=main',
|
|
165
|
+
`--from-happy-cli=${sourceCli}`,
|
|
166
|
+
`--from-happy-cli-base=${base}`,
|
|
167
|
+
'--json',
|
|
168
|
+
],
|
|
169
|
+
{ cwd: process.cwd(), env }
|
|
170
|
+
);
|
|
171
|
+
const parsed = JSON.parse(out.trim());
|
|
172
|
+
assert.equal(parsed.ok, true);
|
|
173
|
+
assert.ok(parsed.results[0].skippedAlreadyApplied >= 1);
|
|
174
|
+
assert.equal((await readFile(join(target, 'apps', 'cli', 'hello.txt'), 'utf-8')).toString(), 'v2\n');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('monorepo port auto-skips identical multi-file new-file patches when all files already exist identically', async (t) => {
|
|
178
|
+
const root = await withTempRoot(t);
|
|
179
|
+
const target = join(root, 'target-mono');
|
|
180
|
+
const sourceCli = join(root, 'source-cli');
|
|
181
|
+
const env = gitEnv();
|
|
182
|
+
|
|
183
|
+
await initMonorepoStub({
|
|
184
|
+
dir: target,
|
|
185
|
+
env,
|
|
186
|
+
seed: { 'apps/cli/a.txt': 'same-a\n', 'apps/cli/b.txt': 'same-b\n' },
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Source: base commit with no a/b, then one commit adding both.
|
|
190
|
+
const base = await initSplitRepoStub({ dir: sourceCli, env, name: 'cli', seed: {} });
|
|
191
|
+
await writeFile(join(sourceCli, 'a.txt'), 'same-a\n', 'utf-8');
|
|
192
|
+
await writeFile(join(sourceCli, 'b.txt'), 'same-b\n', 'utf-8');
|
|
193
|
+
await run('git', ['add', '.'], { cwd: sourceCli, env });
|
|
194
|
+
await run('git', ['commit', '-q', '-m', 'feat: add a + b'], { cwd: sourceCli, env });
|
|
195
|
+
|
|
196
|
+
const out = await runCapture(
|
|
197
|
+
process.execPath,
|
|
198
|
+
[
|
|
199
|
+
join(process.cwd(), 'scripts', 'monorepo.mjs'),
|
|
200
|
+
'port',
|
|
201
|
+
`--target=${target}`,
|
|
202
|
+
`--branch=port/identical-multi-newfiles`,
|
|
203
|
+
'--base=main',
|
|
204
|
+
`--from-happy-cli=${sourceCli}`,
|
|
205
|
+
`--from-happy-cli-base=${base}`,
|
|
206
|
+
'--json',
|
|
207
|
+
],
|
|
208
|
+
{ cwd: process.cwd(), env }
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
const parsed = JSON.parse(out.trim());
|
|
212
|
+
assert.equal(parsed.ok, true);
|
|
213
|
+
assert.equal(parsed.results[0].appliedPatches, 0);
|
|
214
|
+
assert.equal(parsed.results[0].skippedAlreadyExistsIdentical, 1);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test('monorepo port does not auto-skip new-file patch when the target file exists with different content', async (t) => {
|
|
218
|
+
const root = await withTempRoot(t);
|
|
219
|
+
const target = join(root, 'target-mono');
|
|
220
|
+
const sourceCli = join(root, 'source-cli');
|
|
221
|
+
const env = gitEnv();
|
|
222
|
+
|
|
223
|
+
await initMonorepoStub({ dir: target, env, seed: { 'apps/cli/newfile.txt': 'target\n' } });
|
|
224
|
+
const base = await initSplitRepoStub({ dir: sourceCli, env, name: 'cli', seed: {} });
|
|
225
|
+
await writeFile(join(sourceCli, 'newfile.txt'), 'source\n', 'utf-8');
|
|
226
|
+
await run('git', ['add', '.'], { cwd: sourceCli, env });
|
|
227
|
+
await run('git', ['commit', '-q', '-m', 'feat: add newfile'], { cwd: sourceCli, env });
|
|
228
|
+
|
|
229
|
+
const out = await runCapture(
|
|
230
|
+
process.execPath,
|
|
231
|
+
[
|
|
232
|
+
join(process.cwd(), 'scripts', 'monorepo.mjs'),
|
|
233
|
+
'port',
|
|
234
|
+
`--target=${target}`,
|
|
235
|
+
`--branch=port/newfile-differs`,
|
|
236
|
+
'--base=main',
|
|
237
|
+
'--continue-on-failure',
|
|
238
|
+
`--from-happy-cli=${sourceCli}`,
|
|
239
|
+
`--from-happy-cli-base=${base}`,
|
|
240
|
+
'--json',
|
|
241
|
+
],
|
|
242
|
+
{ cwd: process.cwd(), env }
|
|
243
|
+
);
|
|
244
|
+
const parsed = JSON.parse(out.trim());
|
|
245
|
+
assert.equal(parsed.ok, false);
|
|
246
|
+
assert.equal(parsed.results[0].failedPatches, 1);
|
|
247
|
+
assert.equal(parsed.results[0].skippedAlreadyExistsIdentical, 0);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test('monorepo port reports "git am already in progress" even when the target worktree is dirty', async (t) => {
|
|
251
|
+
const root = await withTempRoot(t);
|
|
252
|
+
const target = join(root, 'target-mono');
|
|
253
|
+
const sourceCli = join(root, 'source-cli');
|
|
254
|
+
const env = gitEnv();
|
|
255
|
+
|
|
256
|
+
await initMonorepoStub({ dir: target, env, seed: { 'apps/cli/hello.txt': 'value=target\n' } });
|
|
257
|
+
|
|
258
|
+
// Source CLI repo: base differs from target to force an am conflict.
|
|
259
|
+
await mkdir(sourceCli, { recursive: true });
|
|
260
|
+
await run('git', ['init', '-q'], { cwd: sourceCli, env });
|
|
261
|
+
await run('git', ['checkout', '-q', '-b', 'main'], { cwd: sourceCli, env });
|
|
262
|
+
await writeFile(join(sourceCli, 'package.json'), '{}\n', 'utf-8');
|
|
263
|
+
await writeFile(join(sourceCli, 'hello.txt'), 'value=base\n', 'utf-8');
|
|
264
|
+
await run('git', ['add', '.'], { cwd: sourceCli, env });
|
|
265
|
+
await run('git', ['commit', '-q', '-m', 'chore: init cli'], { cwd: sourceCli, env });
|
|
266
|
+
const base = (await runCapture('git', ['rev-parse', 'HEAD'], { cwd: sourceCli, env })).trim();
|
|
267
|
+
await writeFile(join(sourceCli, 'hello.txt'), 'value=source\n', 'utf-8');
|
|
268
|
+
await run('git', ['add', '.'], { cwd: sourceCli, env });
|
|
269
|
+
await run('git', ['commit', '-q', '-m', 'feat: update hello'], { cwd: sourceCli, env });
|
|
270
|
+
|
|
271
|
+
// Start a port that will stop with an am conflict (leaves git am state).
|
|
272
|
+
await assert.rejects(async () => {
|
|
273
|
+
await runCapture(
|
|
274
|
+
process.execPath,
|
|
275
|
+
[
|
|
276
|
+
join(process.cwd(), 'scripts', 'monorepo.mjs'),
|
|
277
|
+
'port',
|
|
278
|
+
`--target=${target}`,
|
|
279
|
+
`--branch=port/am-in-progress`,
|
|
280
|
+
'--base=main',
|
|
281
|
+
'--3way',
|
|
282
|
+
`--from-happy-cli=${sourceCli}`,
|
|
283
|
+
`--from-happy-cli-base=${base}`,
|
|
284
|
+
],
|
|
285
|
+
{ cwd: process.cwd(), env }
|
|
286
|
+
);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// Make the worktree dirty while am is in progress (this happens naturally for many conflicts,
|
|
290
|
+
// but we force it here to ensure we prefer the more actionable "git am in progress" error).
|
|
291
|
+
await writeFile(join(target, 'apps', 'cli', 'dirty.txt'), 'dirty\n', 'utf-8');
|
|
292
|
+
|
|
293
|
+
// Re-running should complain specifically about the in-progress am.
|
|
294
|
+
await assert.rejects(
|
|
295
|
+
async () => {
|
|
296
|
+
await runCapture(
|
|
297
|
+
process.execPath,
|
|
298
|
+
[
|
|
299
|
+
join(process.cwd(), 'scripts', 'monorepo.mjs'),
|
|
300
|
+
'port',
|
|
301
|
+
`--target=${target}`,
|
|
302
|
+
`--onto-current`,
|
|
303
|
+
`--from-happy-cli=${sourceCli}`,
|
|
304
|
+
`--from-happy-cli-base=${base}`,
|
|
305
|
+
],
|
|
306
|
+
{ cwd: process.cwd(), env }
|
|
307
|
+
);
|
|
308
|
+
},
|
|
309
|
+
(err) => {
|
|
310
|
+
const msg = String(err?.err ?? err?.message ?? err ?? '');
|
|
311
|
+
assert.ok(msg.includes('git am operation is already in progress'), `expected git am in-progress error\n${msg}`);
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test('monorepo port --dry-run does not create a branch or modify the target repo', async (t) => {
|
|
318
|
+
const root = await withTempRoot(t);
|
|
319
|
+
const target = join(root, 'target-mono');
|
|
320
|
+
const sourceCli = join(root, 'source-cli');
|
|
321
|
+
const env = gitEnv();
|
|
322
|
+
|
|
323
|
+
await initMonorepoStub({ dir: target, env, seed: { 'apps/cli/hello.txt': 'v1\n' } });
|
|
324
|
+
const base = await initSplitRepoStub({ dir: sourceCli, env, name: 'cli', seed: { 'hello.txt': 'v1\n' } });
|
|
325
|
+
await writeFile(join(sourceCli, 'hello.txt'), 'v2\n', 'utf-8');
|
|
326
|
+
await run('git', ['add', '.'], { cwd: sourceCli, env });
|
|
327
|
+
await run('git', ['commit', '-q', '-m', 'feat: update hello'], { cwd: sourceCli, env });
|
|
328
|
+
|
|
329
|
+
const beforeHead = (await runCapture('git', ['rev-parse', 'HEAD'], { cwd: target, env })).trim();
|
|
330
|
+
const beforeBranch = (await runCapture('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: target, env })).trim();
|
|
331
|
+
|
|
332
|
+
const out = await runCapture(
|
|
333
|
+
process.execPath,
|
|
334
|
+
[
|
|
335
|
+
join(process.cwd(), 'scripts', 'monorepo.mjs'),
|
|
336
|
+
'port',
|
|
337
|
+
`--target=${target}`,
|
|
338
|
+
'--dry-run',
|
|
339
|
+
`--branch=port/dry-run`,
|
|
340
|
+
`--from-happy-cli=${sourceCli}`,
|
|
341
|
+
`--from-happy-cli-base=${base}`,
|
|
342
|
+
'--json',
|
|
343
|
+
],
|
|
344
|
+
{ cwd: process.cwd(), env }
|
|
345
|
+
);
|
|
346
|
+
const parsed = JSON.parse(out.trim());
|
|
347
|
+
assert.equal(parsed.ok, true);
|
|
348
|
+
assert.equal(parsed.dryRun, true);
|
|
349
|
+
|
|
350
|
+
const afterHead = (await runCapture('git', ['rev-parse', 'HEAD'], { cwd: target, env })).trim();
|
|
351
|
+
const afterBranch = (await runCapture('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: target, env })).trim();
|
|
352
|
+
assert.equal(afterHead, beforeHead);
|
|
353
|
+
assert.equal(afterBranch, beforeBranch);
|
|
354
|
+
|
|
355
|
+
const hasDryBranch = await runCapture('git', ['show-ref', '--verify', '--quiet', 'refs/heads/port/dry-run'], {
|
|
356
|
+
cwd: target,
|
|
357
|
+
env,
|
|
358
|
+
})
|
|
359
|
+
.then(() => true)
|
|
360
|
+
.catch(() => false);
|
|
361
|
+
assert.equal(hasDryBranch, false);
|
|
362
|
+
|
|
363
|
+
assert.equal((await readFile(join(target, 'apps', 'cli', 'hello.txt'), 'utf-8')).toString(), 'v1\n');
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test('monorepo port rejects when --branch already exists in the target repo', async (t) => {
|
|
367
|
+
const root = await withTempRoot(t);
|
|
368
|
+
const target = join(root, 'target-mono');
|
|
369
|
+
const sourceCli = join(root, 'source-cli');
|
|
370
|
+
const env = gitEnv();
|
|
371
|
+
|
|
372
|
+
await initMonorepoStub({ dir: target, env, seed: { 'apps/cli/hello.txt': 'v1\n' } });
|
|
373
|
+
await run('git', ['checkout', '-q', '-b', 'port/existing'], { cwd: target, env });
|
|
374
|
+
await run('git', ['checkout', '-q', 'main'], { cwd: target, env });
|
|
375
|
+
|
|
376
|
+
const base = await initSplitRepoStub({ dir: sourceCli, env, name: 'cli', seed: { 'hello.txt': 'v1\n' } });
|
|
377
|
+
|
|
378
|
+
await assert.rejects(async () => {
|
|
379
|
+
await runCapture(
|
|
380
|
+
process.execPath,
|
|
381
|
+
[
|
|
382
|
+
join(process.cwd(), 'scripts', 'monorepo.mjs'),
|
|
383
|
+
'port',
|
|
384
|
+
`--target=${target}`,
|
|
385
|
+
`--branch=port/existing`,
|
|
386
|
+
'--base=main',
|
|
387
|
+
`--from-happy-cli=${sourceCli}`,
|
|
388
|
+
`--from-happy-cli-base=${base}`,
|
|
389
|
+
],
|
|
390
|
+
{ cwd: process.cwd(), env }
|
|
391
|
+
);
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
test('monorepo port can port from a non-HEAD ref (--from-happy-cli-ref) without changing the source repo checkout', async (t) => {
|
|
396
|
+
const root = await withTempRoot(t);
|
|
397
|
+
const target = join(root, 'target-mono');
|
|
398
|
+
const sourceCli = join(root, 'source-cli');
|
|
399
|
+
const env = gitEnv();
|
|
400
|
+
|
|
401
|
+
await initMonorepoStub({ dir: target, env, seed: { 'apps/cli/hello.txt': 'v1\n' } });
|
|
402
|
+
|
|
403
|
+
await mkdir(sourceCli, { recursive: true });
|
|
404
|
+
await run('git', ['init', '-q'], { cwd: sourceCli, env });
|
|
405
|
+
await run('git', ['checkout', '-q', '-b', 'main'], { cwd: sourceCli, env });
|
|
406
|
+
await writeFile(join(sourceCli, 'package.json'), '{}\n', 'utf-8');
|
|
407
|
+
await writeFile(join(sourceCli, 'hello.txt'), 'v1\n', 'utf-8');
|
|
408
|
+
await run('git', ['add', '.'], { cwd: sourceCli, env });
|
|
409
|
+
await run('git', ['commit', '-q', '-m', 'chore: init cli'], { cwd: sourceCli, env });
|
|
410
|
+
|
|
411
|
+
// Create a feature branch commit, then go back to main to ensure HEAD is not the ref we’re porting.
|
|
412
|
+
await run('git', ['checkout', '-q', '-b', 'feature'], { cwd: sourceCli, env });
|
|
413
|
+
await writeFile(join(sourceCli, 'hello.txt'), 'v2\n', 'utf-8');
|
|
414
|
+
await run('git', ['add', '.'], { cwd: sourceCli, env });
|
|
415
|
+
await run('git', ['commit', '-q', '-m', 'feat: update hello'], { cwd: sourceCli, env });
|
|
416
|
+
await run('git', ['checkout', '-q', 'main'], { cwd: sourceCli, env });
|
|
417
|
+
const headBranch = (await runCapture('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: sourceCli, env })).trim();
|
|
418
|
+
assert.equal(headBranch, 'main');
|
|
419
|
+
|
|
420
|
+
const out = await runCapture(
|
|
421
|
+
process.execPath,
|
|
422
|
+
[
|
|
423
|
+
join(process.cwd(), 'scripts', 'monorepo.mjs'),
|
|
424
|
+
'port',
|
|
425
|
+
`--target=${target}`,
|
|
426
|
+
`--branch=port/from-ref`,
|
|
427
|
+
'--base=main',
|
|
428
|
+
`--from-happy-cli=${sourceCli}`,
|
|
429
|
+
'--from-happy-cli-ref=feature',
|
|
430
|
+
'--from-happy-cli-base=main',
|
|
431
|
+
'--json',
|
|
432
|
+
],
|
|
433
|
+
{ cwd: process.cwd(), env }
|
|
434
|
+
);
|
|
435
|
+
const parsed = JSON.parse(out.trim());
|
|
436
|
+
assert.equal(parsed.ok, true);
|
|
437
|
+
assert.equal((await readFile(join(target, 'apps', 'cli', 'hello.txt'), 'utf-8')).toString(), 'v2\n');
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
test('monorepo port preflight reports conflicts without modifying the target repo', async (t) => {
|
|
441
|
+
const root = await withTempRoot(t);
|
|
442
|
+
const target = join(root, 'target-mono');
|
|
443
|
+
const sourceCli = join(root, 'source-cli');
|
|
444
|
+
const env = gitEnv();
|
|
445
|
+
|
|
446
|
+
// Target monorepo stub with cli/hello.txt="value=target".
|
|
447
|
+
await initMonorepoStub({ dir: target, env, seed: { 'apps/cli/hello.txt': 'value=target\n' } });
|
|
448
|
+
const targetHeadBefore = (await runCapture('git', ['rev-parse', 'HEAD'], { cwd: target, env })).trim();
|
|
449
|
+
|
|
450
|
+
// Source CLI: base commit then feature commit that changes hello.txt (will conflict).
|
|
451
|
+
await mkdir(sourceCli, { recursive: true });
|
|
452
|
+
await run('git', ['init', '-q'], { cwd: sourceCli, env });
|
|
453
|
+
await run('git', ['checkout', '-q', '-b', 'main'], { cwd: sourceCli, env });
|
|
454
|
+
await writeFile(join(sourceCli, 'package.json'), '{}\n', 'utf-8');
|
|
455
|
+
await writeFile(join(sourceCli, 'hello.txt'), 'value=base\n', 'utf-8');
|
|
456
|
+
await run('git', ['add', '.'], { cwd: sourceCli, env });
|
|
457
|
+
await run('git', ['commit', '-q', '-m', 'chore: init cli'], { cwd: sourceCli, env });
|
|
458
|
+
await run('git', ['checkout', '-q', '-b', 'feature'], { cwd: sourceCli, env });
|
|
459
|
+
await writeFile(join(sourceCli, 'hello.txt'), 'value=source\n', 'utf-8');
|
|
460
|
+
await run('git', ['add', '.'], { cwd: sourceCli, env });
|
|
461
|
+
await run('git', ['commit', '-q', '-m', 'feat: update hello'], { cwd: sourceCli, env });
|
|
462
|
+
|
|
463
|
+
const out = await runCapture(
|
|
464
|
+
process.execPath,
|
|
465
|
+
[
|
|
466
|
+
join(process.cwd(), 'scripts', 'monorepo.mjs'),
|
|
467
|
+
'port',
|
|
468
|
+
'preflight',
|
|
469
|
+
`--target=${target}`,
|
|
470
|
+
'--base=main',
|
|
471
|
+
'--json',
|
|
472
|
+
`--from-happy-cli=${sourceCli}`,
|
|
473
|
+
'--from-happy-cli-base=main',
|
|
474
|
+
'--from-happy-cli-ref=feature',
|
|
475
|
+
],
|
|
476
|
+
{ cwd: process.cwd(), env }
|
|
477
|
+
);
|
|
478
|
+
const parsed = JSON.parse(out.trim());
|
|
479
|
+
assert.equal(parsed.ok, false);
|
|
480
|
+
assert.ok(parsed.firstConflict);
|
|
481
|
+
assert.ok(parsed.firstConflict.currentPatch);
|
|
482
|
+
|
|
483
|
+
// Target should remain untouched (preflight runs in a temporary detached worktree).
|
|
484
|
+
const targetHeadAfter = (await runCapture('git', ['rev-parse', 'HEAD'], { cwd: target, env })).trim();
|
|
485
|
+
assert.equal(targetHeadAfter, targetHeadBefore);
|
|
486
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
resolveRunnerLogPathFromRuntime,
|
|
9
|
+
startDaemonPostAuth,
|
|
10
|
+
} from './utils/auth/orchestrated_stack_auth_flow.mjs';
|
|
11
|
+
|
|
12
|
+
function restoreEnvVar(key, prev) {
|
|
13
|
+
if (typeof prev === 'undefined') {
|
|
14
|
+
delete process.env[key];
|
|
15
|
+
} else {
|
|
16
|
+
process.env[key] = prev;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
test('resolveRunnerLogPathFromRuntime returns runtime logs.runner path', async () => {
|
|
21
|
+
const tmp = await mkdtemp(join(tmpdir(), 'hstack-auth-flow-'));
|
|
22
|
+
try {
|
|
23
|
+
const storageDir = join(tmp, 'storage');
|
|
24
|
+
const stackName = 's1';
|
|
25
|
+
const baseDir = join(storageDir, stackName);
|
|
26
|
+
await mkdir(baseDir, { recursive: true });
|
|
27
|
+
|
|
28
|
+
const runnerLogPath = join(tmp, 'runner.log');
|
|
29
|
+
await writeFile(runnerLogPath, 'hello\n', 'utf-8');
|
|
30
|
+
|
|
31
|
+
await writeFile(
|
|
32
|
+
join(baseDir, 'stack.runtime.json'),
|
|
33
|
+
JSON.stringify(
|
|
34
|
+
{
|
|
35
|
+
version: 1,
|
|
36
|
+
stackName,
|
|
37
|
+
ownerPid: process.pid,
|
|
38
|
+
logs: { runner: runnerLogPath },
|
|
39
|
+
},
|
|
40
|
+
null,
|
|
41
|
+
2
|
|
42
|
+
),
|
|
43
|
+
'utf-8'
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const envKey = 'HAPPIER_STACK_STORAGE_DIR';
|
|
47
|
+
const prev = process.env[envKey];
|
|
48
|
+
process.env[envKey] = storageDir;
|
|
49
|
+
try {
|
|
50
|
+
const got = await resolveRunnerLogPathFromRuntime({ stackName, waitMs: 50 });
|
|
51
|
+
assert.equal(got, runnerLogPath);
|
|
52
|
+
} finally {
|
|
53
|
+
restoreEnvVar(envKey, prev);
|
|
54
|
+
}
|
|
55
|
+
} finally {
|
|
56
|
+
await rm(tmp, { recursive: true, force: true });
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('startDaemonPostAuth throws a clear error when runtime server port is missing', async () => {
|
|
61
|
+
const tmp = await mkdtemp(join(tmpdir(), 'hstack-auth-flow-'));
|
|
62
|
+
try {
|
|
63
|
+
const storageDir = join(tmp, 'storage');
|
|
64
|
+
const stackName = 's2';
|
|
65
|
+
const baseDir = join(storageDir, stackName);
|
|
66
|
+
await mkdir(baseDir, { recursive: true });
|
|
67
|
+
|
|
68
|
+
await writeFile(
|
|
69
|
+
join(baseDir, 'stack.runtime.json'),
|
|
70
|
+
JSON.stringify(
|
|
71
|
+
{
|
|
72
|
+
version: 1,
|
|
73
|
+
stackName,
|
|
74
|
+
ownerPid: process.pid,
|
|
75
|
+
ports: {},
|
|
76
|
+
},
|
|
77
|
+
null,
|
|
78
|
+
2
|
|
79
|
+
),
|
|
80
|
+
'utf-8'
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const envKey = 'HAPPIER_STACK_STORAGE_DIR';
|
|
84
|
+
const prev = process.env[envKey];
|
|
85
|
+
process.env[envKey] = storageDir;
|
|
86
|
+
try {
|
|
87
|
+
await assert.rejects(
|
|
88
|
+
() => startDaemonPostAuth({ rootDir: tmp, stackName, env: process.env, forceRestart: true }),
|
|
89
|
+
/could not resolve server port/i
|
|
90
|
+
);
|
|
91
|
+
} finally {
|
|
92
|
+
restoreEnvVar(envKey, prev);
|
|
93
|
+
}
|
|
94
|
+
} finally {
|
|
95
|
+
await rm(tmp, { recursive: true, force: true });
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('resolveRunnerLogPathFromRuntime returns empty string when runtime owner pid is dead', async () => {
|
|
100
|
+
const tmp = await mkdtemp(join(tmpdir(), 'hstack-auth-flow-owner-dead-'));
|
|
101
|
+
try {
|
|
102
|
+
const storageDir = join(tmp, 'storage');
|
|
103
|
+
const stackName = 's3';
|
|
104
|
+
const baseDir = join(storageDir, stackName);
|
|
105
|
+
await mkdir(baseDir, { recursive: true });
|
|
106
|
+
|
|
107
|
+
await writeFile(
|
|
108
|
+
join(baseDir, 'stack.runtime.json'),
|
|
109
|
+
JSON.stringify(
|
|
110
|
+
{
|
|
111
|
+
version: 1,
|
|
112
|
+
stackName,
|
|
113
|
+
ownerPid: 999_999_999,
|
|
114
|
+
logs: {},
|
|
115
|
+
},
|
|
116
|
+
null,
|
|
117
|
+
2
|
|
118
|
+
),
|
|
119
|
+
'utf-8'
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const envKey = 'HAPPIER_STACK_STORAGE_DIR';
|
|
123
|
+
const prev = process.env[envKey];
|
|
124
|
+
process.env[envKey] = storageDir;
|
|
125
|
+
try {
|
|
126
|
+
const got = await resolveRunnerLogPathFromRuntime({ stackName, waitMs: 50, pollMs: 10 });
|
|
127
|
+
assert.equal(got, '');
|
|
128
|
+
} finally {
|
|
129
|
+
restoreEnvVar(envKey, prev);
|
|
130
|
+
}
|
|
131
|
+
} finally {
|
|
132
|
+
await rm(tmp, { recursive: true, force: true });
|
|
133
|
+
}
|
|
134
|
+
});
|