happy-stacks 0.4.0 → 0.5.0
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 +64 -33
- package/bin/happys.mjs +44 -1
- package/docs/codex-mcp-resume.md +130 -0
- package/docs/commit-audits/happy/leeroy-wip.commit-analysis.md +17640 -0
- package/docs/commit-audits/happy/leeroy-wip.commit-export.fuller-stat.md +3845 -0
- package/docs/commit-audits/happy/leeroy-wip.commit-inventory.md +102 -0
- package/docs/commit-audits/happy/leeroy-wip.commit-manual-review.md +1452 -0
- package/docs/commit-audits/happy/leeroy-wip.manual-review-queue.md +116 -0
- package/docs/happy-development.md +1 -2
- package/docs/monorepo-migration.md +286 -0
- package/docs/server-flavors.md +19 -3
- package/docs/stacks.md +35 -0
- package/package.json +1 -1
- package/scripts/auth.mjs +21 -3
- package/scripts/build.mjs +1 -1
- package/scripts/dev.mjs +20 -7
- package/scripts/doctor.mjs +0 -4
- package/scripts/edison.mjs +2 -2
- package/scripts/env.mjs +150 -0
- package/scripts/env_cmd.test.mjs +128 -0
- package/scripts/init.mjs +5 -2
- package/scripts/install.mjs +99 -57
- package/scripts/migrate.mjs +3 -12
- package/scripts/monorepo.mjs +1096 -0
- package/scripts/monorepo_port.test.mjs +1470 -0
- package/scripts/review.mjs +715 -24
- package/scripts/review_pr.mjs +5 -20
- package/scripts/run.mjs +21 -15
- package/scripts/setup.mjs +147 -25
- package/scripts/setup_pr.mjs +19 -28
- package/scripts/stack.mjs +493 -157
- package/scripts/stack_archive_cmd.test.mjs +91 -0
- package/scripts/stack_editor_workspace_monorepo_root.test.mjs +65 -0
- package/scripts/stack_env_cmd.test.mjs +87 -0
- package/scripts/stack_happy_cmd.test.mjs +126 -0
- package/scripts/stack_interactive_monorepo_group.test.mjs +71 -0
- package/scripts/stack_monorepo_defaults.test.mjs +62 -0
- package/scripts/stack_monorepo_server_light_from_happy_spec.test.mjs +66 -0
- package/scripts/stack_server_flavors_defaults.test.mjs +55 -0
- package/scripts/stack_shorthand_cmd.test.mjs +55 -0
- package/scripts/stack_wt_list.test.mjs +128 -0
- package/scripts/tui.mjs +88 -2
- package/scripts/utils/cli/cli_registry.mjs +20 -5
- package/scripts/utils/cli/cwd_scope.mjs +56 -2
- package/scripts/utils/cli/cwd_scope.test.mjs +40 -7
- package/scripts/utils/cli/prereqs.mjs +8 -5
- package/scripts/utils/cli/prereqs.test.mjs +34 -0
- package/scripts/utils/cli/wizard.mjs +17 -9
- package/scripts/utils/cli/wizard_prompt_worktree_source_lazy.test.mjs +60 -0
- package/scripts/utils/dev/daemon.mjs +14 -1
- package/scripts/utils/dev/expo_dev.mjs +188 -4
- package/scripts/utils/dev/server.mjs +21 -17
- package/scripts/utils/edison/git_roots.mjs +29 -0
- package/scripts/utils/edison/git_roots.test.mjs +36 -0
- package/scripts/utils/env/env.mjs +7 -3
- package/scripts/utils/env/env_file.mjs +4 -2
- package/scripts/utils/env/env_file.test.mjs +44 -0
- package/scripts/utils/git/worktrees.mjs +63 -12
- package/scripts/utils/git/worktrees_monorepo.test.mjs +54 -0
- package/scripts/utils/net/tcp_forward.mjs +162 -0
- package/scripts/utils/paths/paths.mjs +118 -3
- package/scripts/utils/paths/paths_monorepo.test.mjs +58 -0
- package/scripts/utils/paths/paths_server_flavors.test.mjs +45 -0
- package/scripts/utils/proc/commands.mjs +2 -3
- package/scripts/utils/proc/pm.mjs +113 -16
- package/scripts/utils/proc/pm_spawn.test.mjs +76 -0
- package/scripts/utils/proc/pm_stack_cache_env.test.mjs +142 -0
- package/scripts/utils/proc/proc.mjs +68 -10
- package/scripts/utils/proc/proc.test.mjs +77 -0
- package/scripts/utils/review/chunks.mjs +55 -0
- package/scripts/utils/review/chunks.test.mjs +51 -0
- package/scripts/utils/review/findings.mjs +165 -0
- package/scripts/utils/review/findings.test.mjs +85 -0
- package/scripts/utils/review/head_slice.mjs +153 -0
- package/scripts/utils/review/head_slice.test.mjs +91 -0
- package/scripts/utils/review/instructions/deep.md +20 -0
- package/scripts/utils/review/runners/coderabbit.mjs +56 -14
- package/scripts/utils/review/runners/coderabbit.test.mjs +59 -0
- package/scripts/utils/review/runners/codex.mjs +32 -22
- package/scripts/utils/review/runners/codex.test.mjs +35 -0
- package/scripts/utils/review/slices.mjs +140 -0
- package/scripts/utils/review/slices.test.mjs +32 -0
- package/scripts/utils/server/flavor_scripts.mjs +98 -0
- package/scripts/utils/server/flavor_scripts.test.mjs +146 -0
- package/scripts/utils/server/prisma_import.mjs +37 -0
- package/scripts/utils/server/prisma_import.test.mjs +70 -0
- package/scripts/utils/server/ui_env.mjs +14 -0
- package/scripts/utils/server/ui_env.test.mjs +46 -0
- package/scripts/utils/server/validate.mjs +53 -16
- package/scripts/utils/server/validate.test.mjs +89 -0
- package/scripts/utils/stack/editor_workspace.mjs +4 -4
- package/scripts/utils/stack/interactive_stack_config.mjs +185 -0
- package/scripts/utils/stack/startup.mjs +113 -13
- package/scripts/utils/stack/startup_server_light_dirs.test.mjs +64 -0
- package/scripts/utils/stack/startup_server_light_generate.test.mjs +70 -0
- package/scripts/utils/stack/startup_server_light_legacy.test.mjs +88 -0
- package/scripts/utils/tailscale/ip.mjs +116 -0
- package/scripts/utils/ui/ansi.mjs +39 -0
- package/scripts/where.mjs +2 -2
- package/scripts/worktrees.mjs +627 -137
- package/scripts/worktrees_archive_cmd.test.mjs +245 -0
- package/scripts/worktrees_cursor_monorepo_root.test.mjs +63 -0
- package/scripts/worktrees_list_specs_no_recurse.test.mjs +33 -0
- package/scripts/worktrees_monorepo_use_group.test.mjs +67 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# leeroy-wip manual review queue (oldest → newest)
|
|
2
|
+
|
|
3
|
+
Generated: 2026-01-24
|
|
4
|
+
Source: docs/commit-audits/happy/leeroy-wip.commit-analysis.md
|
|
5
|
+
|
|
6
|
+
Commits flagged for manual review: 71
|
|
7
|
+
|
|
8
|
+
## Bucket counts
|
|
9
|
+
|
|
10
|
+
- `codex`: 9
|
|
11
|
+
- `cli`: 7
|
|
12
|
+
- `sync`: 5
|
|
13
|
+
- `i18n`: 4
|
|
14
|
+
- `new-session`: 4
|
|
15
|
+
- `claude`: 3
|
|
16
|
+
- `expo-app`: 3
|
|
17
|
+
- `secrets`: 3
|
|
18
|
+
- `tools`: 3
|
|
19
|
+
- `ui`: 3
|
|
20
|
+
- `agent-input`: 2
|
|
21
|
+
- `auth`: 2
|
|
22
|
+
- `permission`: 2
|
|
23
|
+
- `profiles`: 2
|
|
24
|
+
- `resume`: 2
|
|
25
|
+
- `server`: 2
|
|
26
|
+
- `app`: 1
|
|
27
|
+
- `crypto`: 1
|
|
28
|
+
- `experiments`: 1
|
|
29
|
+
- `fork`: 1
|
|
30
|
+
- `happy-cli`: 1
|
|
31
|
+
- `new`: 1
|
|
32
|
+
- `pr107`: 1
|
|
33
|
+
- `reducer`: 1
|
|
34
|
+
- `rpc`: 1
|
|
35
|
+
- `security`: 1
|
|
36
|
+
- `server-light`: 1
|
|
37
|
+
- `settings`: 1
|
|
38
|
+
- `terminal`: 1
|
|
39
|
+
- `tmux`: 1
|
|
40
|
+
- `typecheck`: 1
|
|
41
|
+
|
|
42
|
+
## Queue
|
|
43
|
+
|
|
44
|
+
Each entry is: `index date short-sha bucket — subject — reasons`
|
|
45
|
+
|
|
46
|
+
- 001 2026-01-17 58528a7c37d3 `crypto` — chore(crypto): patch react-native-libsodium — YES (safety/security-sensitive area)
|
|
47
|
+
- 004 2026-01-17 4890a471df31 `auth` — fix(auth): harden tokenStorage web persistence — YES (safety/security-sensitive area)
|
|
48
|
+
- 005 2026-01-17 4adc41d92af0 `sync` — feat(sync): add permission mode types and mapping — YES (safety/security-sensitive area)
|
|
49
|
+
- 017 2026-01-17 65bae55a0b61 `i18n` — refactor(i18n): separate translation types and content — YES (diff summary skipped (large change (+1756/-1006)))
|
|
50
|
+
- 018 2026-01-17 2993b5c860ff `new-session` — fix(new-session): restore standard modal flow — YES (diff summary skipped (large change (+3142/-1629)))
|
|
51
|
+
- 019 2026-01-17 9e08047d2478 `profiles` — fix(profiles): harden routing, grouping, and editing — YES (diff summary skipped (large change (+1319/-857)))
|
|
52
|
+
- 020 2026-01-17 8d9f56e85e5a `ui` — refactor(ui): unify list selectors and modal primitives — YES (diff summary skipped (too many files (28)); safety/security-sensitive area)
|
|
53
|
+
- 022 2026-01-17 2d4675a5d051 `agent-input` — fix(agent-input): use compact permission badges — YES (safety/security-sensitive area)
|
|
54
|
+
- 027 2026-01-18 7899bbd8433e `profiles` — feat(profiles): add API key requirements flow — YES (safety/security-sensitive area)
|
|
55
|
+
- 028 2026-01-18 0023ba1ea5aa `i18n` — refactor(i18n): update translations and tooling — YES (diff summary skipped (large change (+2913/-143)))
|
|
56
|
+
- 033 2026-01-18 daa0b4527d7f `new-session` — feat(new-session): add api key selection and wizard extraction — YES (diff summary skipped (large change (+1719/-1116)))
|
|
57
|
+
- 036 2026-01-18 97ca69e0a735 `i18n` — refactor(i18n): replace remaining UI literals — YES (safety/security-sensitive area)
|
|
58
|
+
- 038 2026-01-18 7976b877259c `experiments` — feat(experiments): gate Zen, file viewer, and voice auth flow — YES (safety/security-sensitive area)
|
|
59
|
+
- 039 2026-01-21 6ed379f82c34 `ui` — refactor(ui): add modal + popover overlay primitives — YES (diff summary skipped (too many files (43)))
|
|
60
|
+
- 040 2026-01-21 f0787de5308e `sync` — feat(sync): add secrets + terminal settings primitives — YES (diff summary skipped (too many files (60)); safety/security-sensitive area)
|
|
61
|
+
- 041 2026-01-21 66ee1eaf88a6 `secrets` — feat(secrets): add secrets management + requirement resolver — YES (diff summary skipped (too many files (29)); safety/security-sensitive area)
|
|
62
|
+
- 043 2026-01-21 bc779e4f7909 `new-session` — refactor(new-session): integrate secrets + terminal spawn options — YES (safety/security-sensitive area)
|
|
63
|
+
- 049 2026-01-21 2e956f32f220 `new` — fix(new): keep pick screens above iOS modal — YES (safety/security-sensitive area)
|
|
64
|
+
- 057 2026-01-21 ff7bec72358f `secrets` — fix(secrets): tighten callback deps and fix indentation — YES (safety/security-sensitive area)
|
|
65
|
+
- 058 2026-01-21 1bb65f5dd494 `new-session` — fix(new-session): avoid stuck secret requirement modal guard — YES (safety/security-sensitive area)
|
|
66
|
+
- 060 2026-01-22 bb09f91de715 `settings` — fix(settings): keep valid secrets when one entry is invalid — YES (safety/security-sensitive area)
|
|
67
|
+
- 061 2026-01-22 765423af52b4 `agent-input` — fix(agent-input): cycle permission mode from normalized state — YES (safety/security-sensitive area)
|
|
68
|
+
- 063 2026-01-22 2ed3100311c7 `secrets` — fix(secrets): hide values when using secret vault — YES (safety/security-sensitive area)
|
|
69
|
+
- 064 2026-01-22 e5848c480522 `ui` — fix(ui): harden overlays and permission cycling — YES (safety/security-sensitive area)
|
|
70
|
+
- 065 2026-01-13 69fdcff96a4c `sync` — fix(sync): restore session permission mode from last message — YES (safety/security-sensitive area)
|
|
71
|
+
- 066 2026-01-13 9b499c5dccce `sync` — fix(sync): persist permission mode timestamp for restart-safe arbitration — YES (safety/security-sensitive area)
|
|
72
|
+
- 067 2026-01-13 def8852509d0 `sync` — fix(sync): persist permission mode reliably across devices — YES (safety/security-sensitive area)
|
|
73
|
+
- 069 2026-01-06 c06b6202b5d4 `expo-app` — Add copy-to-clipboard button to message blocks — YES (non-Conventional-Commits subject)
|
|
74
|
+
- 072 2026-01-21 82d74454c3c4 `typecheck` — fix(typecheck): restore permission imports and Popover web styles — YES (safety/security-sensitive area)
|
|
75
|
+
- 086 2026-01-13 86330e263595 `security` — fix(security): redact spawn secrets from daemon logs — YES (safety/security-sensitive area)
|
|
76
|
+
- 089 2026-01-13 ef418bc4dda3 `pr107` — fix(pr107): redact profile secrets in doctor + align tmux tmpDir — YES (safety/security-sensitive area)
|
|
77
|
+
- 096 2026-01-15 2973f7fe6861 `codex` — fix(codex): harden MCP command detection — YES (safety/security-sensitive area)
|
|
78
|
+
- 099 2026-01-15 c52227082c2b `tmux` — fix(tmux): correct env, tmpdir, and session selection — YES (safety/security-sensitive area)
|
|
79
|
+
- 113 2026-01-21 51782fdbfbcd `codex` — test(codex): reset transport instances between tests — YES (safety/security-sensitive area)
|
|
80
|
+
- 124 2026-01-13 8b88dcd73d40 `claude` — fix(claude): carry permission mode across remote/local switches — YES (safety/security-sensitive area)
|
|
81
|
+
- 125 2026-01-13 8f0e10c9428b `claude` — fix(claude): publish permission mode in session metadata — YES (safety/security-sensitive area)
|
|
82
|
+
- 126 2026-01-13 ffad20faa406 `cli` — fix(cli): publish permission mode for codex/gemini sessions — YES (safety/security-sensitive area)
|
|
83
|
+
- 137 2026-01-12 68a6ba4bc244 `fork` — feat(fork): enable Codex inactive-session resume via codex-reply — YES (safety/security-sensitive area)
|
|
84
|
+
- 140 2026-01-22 9c40c54018c2 `cli` — Revert "fix(tools): support Windows arm64 tool unpacking" — YES (non-Conventional-Commits subject; revert commit)
|
|
85
|
+
- 141 2026-01-22 ab35b47ff699 `resume` — fix(resume): make inactive resume reliable; gate Codex resume — YES (multiple major areas: cli, expo-app; safety/security-sensitive area)
|
|
86
|
+
- 144 2026-01-22 0140ae276869 `codex` — refactor(codex): install mcp resume server via install-dep — YES (multiple major areas: cli, expo-app; safety/security-sensitive area)
|
|
87
|
+
- 146 2026-01-22 e1deb6db8ddb `codex` — refactor(codex): add dep-status and drop codex-resume RPCs — YES (multiple major areas: cli, expo-app)
|
|
88
|
+
- 148 2026-01-22 8bebf04aca92 `cli` — refactor(cli): modularize capabilities and env preview — YES (diff summary skipped (large change (+1296/-908)); safety/security-sensitive area)
|
|
89
|
+
- 150 2026-01-22 a5cac698f15b `cli` — feat(cli): harden session queue, switching, and lifecycle — YES (safety/security-sensitive area)
|
|
90
|
+
- 151 2026-01-22 3fc704424e8d `app` — feat(app): add pending queue, discard markers, and capabilities — YES (diff summary skipped (too many files (44)))
|
|
91
|
+
- 156 2026-01-22 8c16ee8f9cef `auth` — fix(auth): surface auth failures and gate unauth routes — YES (safety/security-sensitive area)
|
|
92
|
+
- 158 2026-01-22 3b3609fed434 `rpc` — fix(rpc): add structured code for missing RPC methods — YES (multiple major areas: expo-app, server)
|
|
93
|
+
- 160 2026-01-22 7fbe1f1cc727 `permission` — fix(permission): avoid no-op permissionModeUpdatedAt bumps — YES (safety/security-sensitive area)
|
|
94
|
+
- 162 2026-01-22 ee0bec2a0b90 `resume` — Delete INACTIVE_SESSION_RESUME.md — YES (non-Conventional-Commits subject)
|
|
95
|
+
- 165 2026-01-23 8b3f39f22664 `cli` — feat(cli): add interaction.respond for AskUserQuestion — YES (safety/security-sensitive area)
|
|
96
|
+
- 166 2025-12-24 ed4bc007308a `codex` — feat: add execpolicy approval option for Codex — YES (safety/security-sensitive area)
|
|
97
|
+
- 167 2025-12-24 78adc9c58811 `codex` — feat(codex): support execpolicy approvals and MCP tool calls — YES (safety/security-sensitive area)
|
|
98
|
+
- 168 2026-01-22 9dfa09bef8d8 `i18n` — fix(i18n): add Codex execpolicy button text — YES (safety/security-sensitive area)
|
|
99
|
+
- 169 2026-01-22 559d39da116d `reducer` — fix(reducer): keep permission messages idempotent — YES (safety/security-sensitive area)
|
|
100
|
+
- 171 2026-01-19 d06d5a833f8e `claude` — Add signal forwarding to claudeLocal.ts — YES (non-Conventional-Commits subject)
|
|
101
|
+
- 174 2025-12-24 e956463db3e2 `codex` — fix: use runtime execPath for MCP bridge — YES (safety/security-sensitive area)
|
|
102
|
+
- 175 2026-01-23 f0a7d8d0b40c `codex` — fix(codex): use mcp tool call id for approvals — YES (safety/security-sensitive area)
|
|
103
|
+
- 176 2026-01-22 da620b6865ad `server` — feat(server): add full/light flavors with sqlite migrations — YES (diff summary skipped (too many files (38)))
|
|
104
|
+
- 183 2026-01-23 46186162ae14 `happy-cli` — test(happy-cli): fix timer cleanup and MCP schema mocks — YES (safety/security-sensitive area)
|
|
105
|
+
- 208 2026-01-23 61a18ac95e17 `tools` — fix(tools): require permission id for ExitPlanMode actions — YES (safety/security-sensitive area)
|
|
106
|
+
- 209 2026-01-23 5b36c9bf91c1 `tools` — test(tools): avoid null permission in ExitPlanToolView tests — YES (safety/security-sensitive area)
|
|
107
|
+
- 211 2026-01-23 55428fb0253c `tools` — fix(tools): alert when AskUserQuestion permission id is missing — YES (safety/security-sensitive area)
|
|
108
|
+
- 212 2026-01-23 dc6955f88abd `terminal` — fix(terminal): prevent sessionId path traversal in attachment info — YES (safety/security-sensitive area)
|
|
109
|
+
- 226 2026-01-23 c461ed1cb4ef `permission` — feat(permission): add permission mode option helpers — YES (safety/security-sensitive area)
|
|
110
|
+
- 228 2026-01-23 523989b4de8e `server-light` — fix(server-light): avoid master secret race — YES (safety/security-sensitive area)
|
|
111
|
+
- 237 2026-01-23 241f0ae24b1d `cli` — test(cli): prevent legacy sessionId path traversal — YES (safety/security-sensitive area)
|
|
112
|
+
- 238 2026-01-23 a1e4a6dd3fbd `cli` — fix(cli): block legacy sessionId path traversal — YES (safety/security-sensitive area)
|
|
113
|
+
- 241 2026-01-23 1464402758bf `codex` — fix(codex): preserve falsy MCP tool results — YES (safety/security-sensitive area)
|
|
114
|
+
- 247 2026-01-23 24b607abfa07 `server` — Refactor schema sync and centralize Prisma types — YES (non-Conventional-Commits subject; diff summary skipped (too many files (29)))
|
|
115
|
+
- 248 2026-01-23 bf3027799623 `expo-app` — Set EXPO_UNSTABLE_WEB_MODAL env var in Expo scripts — YES (non-Conventional-Commits subject)
|
|
116
|
+
- 249 2026-01-23 c803115dcbdc `expo-app` — Improve postinstall script for symlinked paths and patching — YES (non-Conventional-Commits subject)
|
|
@@ -375,7 +375,7 @@ In interactive TTY runs, `happys dev` / `happys start` may auto-open the UI in y
|
|
|
375
375
|
- **Dependency install**: ensures component deps are installed when needed.
|
|
376
376
|
- **Schema readiness**:
|
|
377
377
|
- `happy-server` (Postgres): applies `prisma migrate deploy` (configurable via `HAPPY_STACKS_PRISMA_MIGRATE`)
|
|
378
|
-
- `happy-server-light` (SQLite):
|
|
378
|
+
- `happy-server-light` (SQLite): applies `prisma migrate deploy` using the SQLite migration history in the unified server repo
|
|
379
379
|
- **Auth seeding for new stacks** (non-main + non-interactive default):
|
|
380
380
|
- Uses the configured seed stack via `HAPPY_STACKS_AUTH_SEED_FROM` (default: `main`) when the stack looks uninitialized.
|
|
381
381
|
- Recommended for development: create + log into a dedicated seed stack once (usually `dev-auth`) and set:
|
|
@@ -628,7 +628,6 @@ Happy Stacks uses `HAPPY_STACKS_*` as the canonical prefix; most settings also a
|
|
|
628
628
|
- **Full server infra** (happy-server):
|
|
629
629
|
- `HAPPY_STACKS_MANAGED_INFRA=0` (disable Docker-managed Postgres/Redis/Minio; provide URLs yourself)
|
|
630
630
|
- **Prisma behavior**:
|
|
631
|
-
- `HAPPY_STACKS_PRISMA_PUSH=0` (server-light: avoid `prisma db push` in dev mode)
|
|
632
631
|
- `HAPPY_STACKS_PRISMA_MIGRATE=0` (full server: disable `prisma migrate deploy`)
|
|
633
632
|
- **happy-cli build behavior**:
|
|
634
633
|
- `HAPPY_STACKS_CLI_BUILD_MODE=auto|always|never`
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# Monorepo migration (split repos → `slopus/happy`)
|
|
2
|
+
|
|
3
|
+
This doc explains the **recommended, safe, step-by-step** flow to port commits from the legacy split repos (`happy`, `happy-cli`, `happy-server`) into the new `slopus/happy` monorepo layout:
|
|
4
|
+
|
|
5
|
+
- old `happy` (UI) → `expo-app/`
|
|
6
|
+
- old `happy-cli` → `cli/`
|
|
7
|
+
- old `happy-server` → `server/`
|
|
8
|
+
|
|
9
|
+
The tooling used here is `happys monorepo port` (from this repo / Happy Stacks). It ports commits by generating patches (`git format-patch`) and applying them with `git am` into the target monorepo branch.
|
|
10
|
+
|
|
11
|
+
## Quick start (if you don’t already use Happy Stacks)
|
|
12
|
+
|
|
13
|
+
If you’re an external collaborator and you **don’t** have `happys` installed yet, this is the fastest “migration environment” setup:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx happy-stacks init --install-path
|
|
17
|
+
happys bootstrap --interactive
|
|
18
|
+
happys stack new monorepo-merge --interactive
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### “No install” option (npx-only, port command only)
|
|
22
|
+
|
|
23
|
+
If you *only* want to run the port helper (and you already have local clones of the source repos + target monorepo), you can run it directly via `npx` without installing anything globally:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx --yes happy-stacks monorepo port --help
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Example:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx --yes happy-stacks monorepo port \
|
|
33
|
+
--target=/abs/path/to/slopus-happy-monorepo \
|
|
34
|
+
--branch=your-port-branch \
|
|
35
|
+
--base=origin/main \
|
|
36
|
+
--3way \
|
|
37
|
+
--from-happy=/abs/path/to/old-happy \
|
|
38
|
+
--from-happy-base=origin/main
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Notes:
|
|
42
|
+
- This “npx-only” mode is great for **one-off ports**, but it won’t manage stacks/worktrees for you.
|
|
43
|
+
- For the full guided flow (stacks + worktrees + repeatable commands), use the installed setup above.
|
|
44
|
+
|
|
45
|
+
### Port helpers (guide / status / continue)
|
|
46
|
+
|
|
47
|
+
Happy Stacks also provides small helpers that make conflict resolution less error-prone:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
happys monorepo port guide
|
|
51
|
+
happys monorepo port status --target=/abs/path/to/monorepo
|
|
52
|
+
happys monorepo port continue --target=/abs/path/to/monorepo
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- `port guide` is interactive (TTY required) and helps you build the initial port command safely.
|
|
56
|
+
- `port guide` can also **pause on conflicts**, let you resolve them, and then resume until the port completes.
|
|
57
|
+
- `port status` shows whether a `git am` session is in progress, the current patch subject, and conflicted files.
|
|
58
|
+
- `port continue` runs `git am --continue` for the target repo (after you staged resolved files). If you started from `port guide`, it can also resume the remaining port automatically after each continue.
|
|
59
|
+
|
|
60
|
+
How to bring “the changes you want to port” into this environment:
|
|
61
|
+
|
|
62
|
+
- **If you already have local checkouts** of your legacy repos (recommended for forks/branches that aren’t PRs yet):
|
|
63
|
+
- keep them wherever they are
|
|
64
|
+
- you’ll pass their absolute paths to `happys monorepo port` via `--from-happy=...`, `--from-happy-cli=...`, etc.
|
|
65
|
+
|
|
66
|
+
- **If your changes exist as GitHub PRs**, you can let Happy Stacks create a clean worktree from the PR directly:
|
|
67
|
+
- `happys wt pr happy-cli <pr-url-or-number> --use`
|
|
68
|
+
- `happys wt pr happy <pr-url-or-number> --use`
|
|
69
|
+
- (repeat for whichever repos your changes live in)
|
|
70
|
+
|
|
71
|
+
Once you have:
|
|
72
|
+
- a **target monorepo worktree** (from upstream), and
|
|
73
|
+
- one or more **source repos** (paths or worktrees)
|
|
74
|
+
|
|
75
|
+
…continue with the rest of this doc.
|
|
76
|
+
|
|
77
|
+
## Prereqs
|
|
78
|
+
|
|
79
|
+
- You have a **clean** source worktree/branch for each legacy repo you want to port.
|
|
80
|
+
- No uncommitted changes (unless you intentionally want to include them as new commits first).
|
|
81
|
+
- Ideally based on `upstream/main` (or you know the correct base ref).
|
|
82
|
+
- You have a **clean** target monorepo checkout (a worktree of `slopus/happy`).
|
|
83
|
+
- You are ready to resolve conflicts with `git am` (this is normal for large refactors, i18n changes, renamed files, etc).
|
|
84
|
+
|
|
85
|
+
## Recommended workflow (interactive, safest)
|
|
86
|
+
|
|
87
|
+
### 1) Create an isolated stack (don’t touch `main`)
|
|
88
|
+
|
|
89
|
+
Pick a new stack name (example: `monorepo-merge`):
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
happys stack new monorepo-merge --interactive
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 2) Create a clean monorepo worktree from upstream
|
|
96
|
+
|
|
97
|
+
Create a worktree based on `upstream/main` for the **monorepo** (`slopus/happy`):
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
happys wt new happy tmp/monorepo-port --from=upstream
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Point your stack at that worktree (this keeps `main` stable):
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
happys stack wt monorepo-merge -- use happy /absolute/path/to/components/.worktrees/happy/slopus/tmp/monorepo-port
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Notes:
|
|
110
|
+
- In monorepo mode, `happy`, `happy-cli`, and `happy-server` are **one git repo**; the stack overrides should point at the same monorepo root.
|
|
111
|
+
- `happys ... wt use happy <monorepo-root>` automatically updates all three component dir overrides together (prevents UI/CLI/server version skew).
|
|
112
|
+
- If you pass a monorepo root, Happy Stacks normalizes component dirs to:
|
|
113
|
+
- `happy` → `.../expo-app`
|
|
114
|
+
- `happy-cli` → `.../cli`
|
|
115
|
+
- `happy-server` → `.../server`
|
|
116
|
+
|
|
117
|
+
### 3) Create a target branch
|
|
118
|
+
|
|
119
|
+
In the monorepo worktree, create your migration branch from `upstream/main`:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
happys wt git happy slopus/tmp/monorepo-port -- checkout -b <your-branch-name> upstream/main
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Example:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
happys wt git happy slopus/tmp/monorepo-port -- checkout -b leeroy-wip upstream/main
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 4) Port the UI commits (old `happy` → `expo-app/`)
|
|
132
|
+
|
|
133
|
+
Run the port in **interactive mode**:
|
|
134
|
+
|
|
135
|
+
- use `--onto-current` to apply onto the branch you already checked out
|
|
136
|
+
- use `--3way` so git can do a 3-way merge and produce conflict markers instead of failing immediately
|
|
137
|
+
- **do not** use `--continue-on-failure` (you want it to stop at the first conflict)
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
happys monorepo port \
|
|
141
|
+
--target=/abs/path/to/monorepo-root \
|
|
142
|
+
--onto-current \
|
|
143
|
+
--3way \
|
|
144
|
+
--from-happy=/abs/path/to/old-happy-repo \
|
|
145
|
+
--from-happy-base=upstream/main
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
What `--from-happy-base` means:
|
|
149
|
+
- It’s the ref used to compute the patch range (`merge-base(base, HEAD)..HEAD`).
|
|
150
|
+
- If your branch is based on `upstream/main`, this should be `upstream/main`.
|
|
151
|
+
|
|
152
|
+
### 5) Resolve conflicts (when the port stops)
|
|
153
|
+
|
|
154
|
+
If the port stops, you are now in a normal `git am` session.
|
|
155
|
+
|
|
156
|
+
Helpful commands:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
git am --show-current-patch=diff
|
|
160
|
+
git status
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Fix conflicts in the files with conflict markers:
|
|
164
|
+
- look for `<<<<<<<`, `=======`, `>>>>>>>`
|
|
165
|
+
- edit to the desired final content
|
|
166
|
+
- then stage the resolved files:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
git add <file...>
|
|
170
|
+
git am --continue
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
If you decide a specific patch should not be ported:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
git am --skip
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
If you want to fully abort the current patch application:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
git am --abort
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Then rerun the `happys monorepo port ... --onto-current ...` command.
|
|
186
|
+
|
|
187
|
+
### 6) Port the CLI commits (old `happy-cli` → `cli/`)
|
|
188
|
+
|
|
189
|
+
After the UI port completes, port CLI commits onto the same branch:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
happys monorepo port \
|
|
193
|
+
--target=/abs/path/to/monorepo-root \
|
|
194
|
+
--onto-current \
|
|
195
|
+
--3way \
|
|
196
|
+
--from-happy-cli=/abs/path/to/old-happy-cli-repo \
|
|
197
|
+
--from-happy-cli-base=upstream/main
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Resolve conflicts the same way (`git am --continue`).
|
|
201
|
+
|
|
202
|
+
### 7) Verify what landed
|
|
203
|
+
|
|
204
|
+
Quick sanity checks:
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
# ensure upstream/main is an ancestor of your branch
|
|
208
|
+
git merge-base --is-ancestor upstream/main HEAD
|
|
209
|
+
|
|
210
|
+
# list commits introduced by the port
|
|
211
|
+
git log --oneline upstream/main..HEAD
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
If you are porting multiple legacy branches, it’s often useful to compare counts:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
git rev-list --count upstream/main..HEAD
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### 8) Push and open a PR
|
|
221
|
+
|
|
222
|
+
Push to the remote you intend to PR against (example uses `upstream`):
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
git push upstream HEAD:<branch-name>
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Common failures (expected) and what to do
|
|
229
|
+
|
|
230
|
+
### “target repo is not clean”
|
|
231
|
+
|
|
232
|
+
`happys monorepo port` refuses to run when the target has local changes.
|
|
233
|
+
|
|
234
|
+
Fix: commit, stash, or reset your target worktree, then re-run.
|
|
235
|
+
|
|
236
|
+
### “a git am operation is already in progress”
|
|
237
|
+
|
|
238
|
+
You have an unfinished `git am` session from a previous attempt.
|
|
239
|
+
|
|
240
|
+
Fix it first:
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
git am --continue # after resolving conflicts
|
|
244
|
+
# or
|
|
245
|
+
git am --abort
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Then re-run `happys monorepo port ...`.
|
|
249
|
+
|
|
250
|
+
### “patch does not apply” / i18n churn / renamed files
|
|
251
|
+
|
|
252
|
+
This usually means upstream moved and the patch context no longer matches.
|
|
253
|
+
|
|
254
|
+
Recommended: rerun with `--3way` (3-way merge) so you get conflict markers to resolve.
|
|
255
|
+
|
|
256
|
+
### “already exists in working directory”
|
|
257
|
+
|
|
258
|
+
This often happens when a commit (especially “new file”) was already folded into the monorepo history.
|
|
259
|
+
|
|
260
|
+
The tool auto-skips:
|
|
261
|
+
- patches that are already present (exact-match reverse apply check)
|
|
262
|
+
- pure new-file patches when the target already contains identical content
|
|
263
|
+
|
|
264
|
+
If you still hit this manually during a stopped `git am`, decide whether to:
|
|
265
|
+
- keep the existing file and `git am --skip`, or
|
|
266
|
+
- reconcile content and continue.
|
|
267
|
+
|
|
268
|
+
### Missing file path errors
|
|
269
|
+
|
|
270
|
+
If the patch references files that no longer exist (or moved) in the monorepo, you’ll need to:
|
|
271
|
+
- map the change to the new file location, or
|
|
272
|
+
- skip that patch if it’s obsolete.
|
|
273
|
+
|
|
274
|
+
Use `git am --show-current-patch=diff` to understand intent, then implement the equivalent change in the monorepo layout and continue.
|
|
275
|
+
|
|
276
|
+
## Optional: audit mode (best-effort report)
|
|
277
|
+
|
|
278
|
+
If you want a full report of what would apply vs fail (without stopping at the first conflict), you can run:
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
happys monorepo port ... --continue-on-failure --json
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
This is **not** the recommended way to produce a final clean branch, but it can be useful to:
|
|
285
|
+
- discover the full set of expected conflicts
|
|
286
|
+
- share a machine-readable report for assistance
|
package/docs/server-flavors.md
CHANGED
|
@@ -4,7 +4,21 @@ Happy Stacks supports two server “flavors”. You can switch between them glob
|
|
|
4
4
|
|
|
5
5
|
## What’s the difference?
|
|
6
6
|
|
|
7
|
-
Both are forks/flavors of the same upstream server repo (`slopus/happy-server`), but optimized for different use cases
|
|
7
|
+
Both are forks/flavors of the same upstream server repo (`slopus/happy-server`), but optimized for different use cases.
|
|
8
|
+
|
|
9
|
+
### Unified codebase (recommended)
|
|
10
|
+
|
|
11
|
+
When your `happy-server` checkout includes the light flavor artifacts (notably `prisma/sqlite/schema.prisma` — legacy: `prisma/schema.sqlite.prisma`), Happy Stacks treats it as a **single unified server codebase** that supports both:
|
|
12
|
+
|
|
13
|
+
- `happy-server` (full / Postgres+Redis+S3)
|
|
14
|
+
- `happy-server-light` (light / SQLite+local files, can serve UI)
|
|
15
|
+
|
|
16
|
+
In that setup:
|
|
17
|
+
|
|
18
|
+
- there is **no server code duplication**
|
|
19
|
+
- `happy-server-light` can point at the **same checkout/worktree** as `happy-server`
|
|
20
|
+
- `happys stack new` will default to pinning **both** server component dirs to the same path
|
|
21
|
+
- `happys start/dev --server=happy-server-light` will run `start:light` / `dev:light` when available
|
|
8
22
|
|
|
9
23
|
- **`happy-server-light`** (recommended default)
|
|
10
24
|
- optimized for local usage
|
|
@@ -73,7 +87,7 @@ Notes:
|
|
|
73
87
|
- **`happys start`** is “production-like”. It avoids running heavyweight schema sync loops under launchd KeepAlive.
|
|
74
88
|
- **`happys dev`** is for rapid iteration:
|
|
75
89
|
- for `happy-server`: Happy Stacks runs `prisma migrate deploy` by default (configurable via `HAPPY_STACKS_PRISMA_MIGRATE`).
|
|
76
|
-
- for `happy-server-light`:
|
|
90
|
+
- for `happy-server-light`: Happy Stacks runs `prisma migrate deploy` (SQLite migrations) using the unified schema under `prisma/sqlite/schema.prisma`.
|
|
77
91
|
|
|
78
92
|
Important: for a given run (`happys start` / `happys dev`) you choose **one** flavor.
|
|
79
93
|
|
|
@@ -125,7 +139,9 @@ There are two separate concepts:
|
|
|
125
139
|
- controlled by `HAPPY_STACKS_COMPONENT_DIR_HAPPY_SERVER_LIGHT` and `HAPPY_STACKS_COMPONENT_DIR_HAPPY_SERVER`
|
|
126
140
|
- easiest via `happys wt use happy-server-light ...` / `happys wt use happy-server ...`
|
|
127
141
|
|
|
128
|
-
If you set `HAPPY_STACKS_SERVER_COMPONENT=happy-server-light` but accidentally point the *server-light component dir* at a `happy-server`
|
|
142
|
+
If you set `HAPPY_STACKS_SERVER_COMPONENT=happy-server-light` but accidentally point the *server-light component dir* at a postgres-only `happy-server` checkout (or vice versa), `happys start/dev/doctor` will refuse to run and print a fix hint.
|
|
143
|
+
|
|
144
|
+
In unified mode (same repo supports both flavors), it is valid (and recommended) for both component dirs to point at the same checkout.
|
|
129
145
|
|
|
130
146
|
`happys wt use` also prevents the most common mismatch when selecting server worktrees inside `components/` / `components/.worktrees/`.
|
|
131
147
|
|
package/docs/stacks.md
CHANGED
|
@@ -238,6 +238,41 @@ Cloned-repo fallback (before you run `happys init`):
|
|
|
238
238
|
2. `<repo>/env.local` (optional overrides)
|
|
239
239
|
3. `HAPPY_STACKS_ENV_FILE` (stack env)
|
|
240
240
|
|
|
241
|
+
## Manage per-stack environment variables (including API keys)
|
|
242
|
+
|
|
243
|
+
To add/update environment variables in a stack env file from the CLI:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
happys stack env <stack> set KEY=VALUE [KEY2=VALUE2...]
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
To remove keys:
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
happys stack env <stack> unset KEY [KEY2...]
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
To inspect:
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
happys stack env <stack> get KEY
|
|
259
|
+
happys stack env <stack> list
|
|
260
|
+
happys stack env <stack> path
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Notes:
|
|
264
|
+
|
|
265
|
+
- This is the recommended place for **provider API keys** the daemon needs (example: `OPENAI_API_KEY`).
|
|
266
|
+
- Changes apply on the **next start** of the stack/daemon. Restart to pick them up:
|
|
267
|
+
- `main`: `happys start --restart`
|
|
268
|
+
- named stack: `happys stack start <stack> -- --restart` (or `happys stack dev <stack> -- --restart`)
|
|
269
|
+
|
|
270
|
+
Self-host shortcut (defaults to `main` when not running under a stack wrapper):
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
happys env set OPENAI_API_KEY=sk-...
|
|
274
|
+
```
|
|
275
|
+
|
|
241
276
|
## Daemon auth + “no machine” on first run
|
|
242
277
|
|
|
243
278
|
On a **fresh machine** (or any new stack), the daemon may need to authenticate before it can register a “machine”.
|
package/package.json
CHANGED
package/scripts/auth.mjs
CHANGED
|
@@ -16,6 +16,7 @@ import { dirname } from 'node:path';
|
|
|
16
16
|
import { parseEnvToObject } from './utils/env/dotenv.mjs';
|
|
17
17
|
import { ensureDepsInstalled, pmExecBin } from './utils/proc/pm.mjs';
|
|
18
18
|
import { applyHappyServerMigrations, ensureHappyServerManagedInfra } from './utils/server/infra/happy_server_infra.mjs';
|
|
19
|
+
import { resolvePrismaClientImportForServerComponent, resolveServerLightPrismaMigrateDeployArgs } from './utils/server/flavor_scripts.mjs';
|
|
19
20
|
import { clearDevAuthKey, readDevAuthKey, writeDevAuthKey } from './utils/auth/dev_key.mjs';
|
|
20
21
|
import { getExpoStatePaths, isStateProcessRunning } from './utils/expo/expo.mjs';
|
|
21
22
|
import { resolveAuthSeedFromEnv } from './utils/stack/startup.mjs';
|
|
@@ -227,6 +228,15 @@ async function seedAccountsFromSourceDbToTargetDb({
|
|
|
227
228
|
const sourceCwd = resolveServerComponentDir({ rootDir, serverComponent: fromServerComponent });
|
|
228
229
|
const targetCwd = resolveServerComponentDir({ rootDir, serverComponent: targetServerComponent });
|
|
229
230
|
|
|
231
|
+
const sourceClientImport = resolvePrismaClientImportForServerComponent({
|
|
232
|
+
serverComponentName: fromServerComponent,
|
|
233
|
+
serverDir: sourceCwd,
|
|
234
|
+
});
|
|
235
|
+
const targetClientImport = resolvePrismaClientImportForServerComponent({
|
|
236
|
+
serverComponentName: targetServerComponent,
|
|
237
|
+
serverDir: targetCwd,
|
|
238
|
+
});
|
|
239
|
+
|
|
230
240
|
const listScript = `
|
|
231
241
|
process.on('uncaughtException', (e) => {
|
|
232
242
|
console.error(e instanceof Error ? e.message : String(e));
|
|
@@ -236,7 +246,11 @@ process.on('unhandledRejection', (e) => {
|
|
|
236
246
|
console.error(e instanceof Error ? e.message : String(e));
|
|
237
247
|
process.exit(1);
|
|
238
248
|
});
|
|
239
|
-
|
|
249
|
+
const mod = await import(${JSON.stringify(sourceClientImport)});
|
|
250
|
+
const PrismaClient = mod?.PrismaClient ?? mod?.default?.PrismaClient;
|
|
251
|
+
if (!PrismaClient) {
|
|
252
|
+
throw new Error('Failed to load PrismaClient for DB seed (source).');
|
|
253
|
+
}
|
|
240
254
|
const db = new PrismaClient();
|
|
241
255
|
try {
|
|
242
256
|
const accounts = await db.account.findMany({ select: { id: true, publicKey: true } });
|
|
@@ -255,7 +269,11 @@ process.on('unhandledRejection', (e) => {
|
|
|
255
269
|
console.error(e instanceof Error ? e.message : String(e));
|
|
256
270
|
process.exit(1);
|
|
257
271
|
});
|
|
258
|
-
|
|
272
|
+
const mod = await import(${JSON.stringify(targetClientImport)});
|
|
273
|
+
const PrismaClient = mod?.PrismaClient ?? mod?.default?.PrismaClient;
|
|
274
|
+
if (!PrismaClient) {
|
|
275
|
+
throw new Error('Failed to load PrismaClient for DB seed (target).');
|
|
276
|
+
}
|
|
259
277
|
import fs from 'node:fs';
|
|
260
278
|
const FORCE = ${force ? 'true' : 'false'};
|
|
261
279
|
const raw = fs.readFileSync(0, 'utf8').trim();
|
|
@@ -633,7 +651,7 @@ async function cmdCopyFrom({ argv, json }) {
|
|
|
633
651
|
await pmExecBin({
|
|
634
652
|
dir: serverDirForPrisma,
|
|
635
653
|
bin: 'prisma',
|
|
636
|
-
args:
|
|
654
|
+
args: resolveServerLightPrismaMigrateDeployArgs({ serverDir: serverDirForPrisma }),
|
|
637
655
|
env: { ...process.env, DATABASE_URL: targetDatabaseUrl },
|
|
638
656
|
quiet: json,
|
|
639
657
|
}).catch(() => {});
|
package/scripts/build.mjs
CHANGED
|
@@ -15,7 +15,7 @@ import { getInvokedCwd, inferComponentFromCwd } from './utils/cli/cwd_scope.mjs'
|
|
|
15
15
|
* Build a lightweight static web UI bundle (no Expo dev server).
|
|
16
16
|
*
|
|
17
17
|
* Output directory default: ~/.happy/stacks/main/ui (legacy: ~/.happy/local/ui)
|
|
18
|
-
* Server will serve it at / when
|
|
18
|
+
* Server will serve it at / when HAPPY_SERVER_UI_DIR is set.
|
|
19
19
|
* (Legacy /ui paths are redirected to /.)
|
|
20
20
|
*/
|
|
21
21
|
|