happy-stacks 0.2.0 → 0.4.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 +84 -25
- package/bin/happys.mjs +116 -17
- package/docs/happy-development.md +2 -2
- package/docs/isolated-linux-vm.md +82 -0
- package/docs/mobile-ios.md +112 -54
- package/package.json +5 -1
- package/scripts/auth.mjs +59 -208
- package/scripts/build.mjs +58 -12
- package/scripts/cli-link.mjs +3 -3
- package/scripts/completion.mjs +5 -5
- package/scripts/daemon.mjs +168 -20
- package/scripts/dev.mjs +196 -70
- package/scripts/doctor.mjs +20 -36
- package/scripts/edison.mjs +105 -78
- package/scripts/happy.mjs +8 -19
- package/scripts/init.mjs +8 -14
- package/scripts/install.mjs +119 -23
- package/scripts/lint.mjs +31 -32
- package/scripts/menubar.mjs +6 -13
- package/scripts/migrate.mjs +11 -21
- package/scripts/mobile.mjs +93 -108
- package/scripts/mobile_dev_client.mjs +83 -0
- package/scripts/provision/linux-ubuntu-review-pr.sh +51 -0
- package/scripts/review.mjs +217 -0
- package/scripts/review_pr.mjs +368 -0
- package/scripts/run.mjs +95 -21
- package/scripts/self.mjs +11 -29
- package/scripts/server_flavor.mjs +4 -4
- package/scripts/service.mjs +19 -29
- package/scripts/setup.mjs +63 -160
- package/scripts/setup_pr.mjs +592 -52
- package/scripts/stack.mjs +608 -200
- package/scripts/stop.mjs +3 -3
- package/scripts/tailscale.mjs +44 -11
- package/scripts/test.mjs +52 -36
- package/scripts/tui.mjs +314 -74
- package/scripts/typecheck.mjs +31 -32
- package/scripts/ui_gateway.mjs +1 -1
- package/scripts/uninstall.mjs +6 -6
- package/scripts/utils/auth/daemon_gate.mjs +55 -0
- package/scripts/utils/auth/daemon_gate.test.mjs +37 -0
- package/scripts/utils/auth/dev_key.mjs +163 -0
- package/scripts/utils/{auth_files.mjs → auth/files.mjs} +2 -4
- package/scripts/utils/auth/guided_pr_auth.mjs +79 -0
- package/scripts/utils/auth/guided_stack_web_login.mjs +75 -0
- package/scripts/utils/auth/handy_master_secret.mjs +68 -0
- package/scripts/utils/auth/interactive_stack_auth.mjs +72 -0
- package/scripts/utils/{auth_login_ux.mjs → auth/login_ux.mjs} +32 -13
- package/scripts/utils/auth/sources.mjs +38 -0
- package/scripts/utils/auth/stack_guided_login.mjs +353 -0
- package/scripts/utils/cli/cli_registry.mjs +24 -0
- package/scripts/utils/cli/cwd_scope.mjs +82 -0
- package/scripts/utils/cli/cwd_scope.test.mjs +77 -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 +72 -0
- package/scripts/utils/cli/progress.mjs +126 -0
- package/scripts/utils/cli/smoke_help.mjs +2 -2
- package/scripts/utils/cli/verbosity.mjs +12 -0
- package/scripts/utils/cli/wizard.mjs +1 -1
- package/scripts/utils/crypto/tokens.mjs +14 -0
- package/scripts/utils/{dev_daemon.mjs → dev/daemon.mjs} +51 -7
- package/scripts/utils/dev/expo_dev.mjs +246 -0
- package/scripts/utils/dev/expo_dev.test.mjs +76 -0
- package/scripts/utils/{dev_server.mjs → dev/server.mjs} +22 -32
- package/scripts/utils/dev_auth_key.mjs +1 -1
- package/scripts/utils/{config.mjs → env/config.mjs} +3 -2
- package/scripts/utils/{dotenv.mjs → env/dotenv.mjs} +3 -0
- package/scripts/utils/{env.mjs → env/env.mjs} +5 -3
- package/scripts/utils/{env_file.mjs → env/env_file.mjs} +2 -1
- package/scripts/utils/{env_local.mjs → env/env_local.mjs} +1 -0
- package/scripts/utils/env/read.mjs +30 -0
- package/scripts/utils/env/values.mjs +13 -0
- package/scripts/utils/expo/command.mjs +52 -0
- package/scripts/utils/{expo.mjs → expo/expo.mjs} +23 -10
- package/scripts/utils/expo/metro_ports.mjs +114 -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/git.mjs +67 -0
- package/scripts/utils/git/refs.mjs +26 -0
- package/scripts/utils/{worktrees.mjs → git/worktrees.mjs} +27 -23
- package/scripts/utils/handy_master_secret.mjs +2 -2
- package/scripts/utils/mobile/config.mjs +31 -0
- package/scripts/utils/mobile/dev_client_links.mjs +60 -0
- package/scripts/utils/mobile/identifiers.mjs +47 -0
- package/scripts/utils/mobile/identifiers.test.mjs +42 -0
- package/scripts/utils/mobile/ios_xcodeproj_patch.mjs +128 -0
- package/scripts/utils/mobile/ios_xcodeproj_patch.test.mjs +98 -0
- package/scripts/utils/net/dns.mjs +10 -0
- package/scripts/utils/net/lan_ip.mjs +24 -0
- package/scripts/utils/{ports.mjs → net/ports.mjs} +12 -6
- package/scripts/utils/net/url.mjs +30 -0
- package/scripts/utils/net/url.test.mjs +20 -0
- package/scripts/utils/paths/localhost_host.mjs +56 -0
- package/scripts/utils/{paths.mjs → paths/paths.mjs} +52 -45
- package/scripts/utils/{runtime.mjs → paths/runtime.mjs} +3 -1
- package/scripts/utils/proc/commands.mjs +34 -0
- package/scripts/utils/{ownership.mjs → proc/ownership.mjs} +1 -1
- package/scripts/utils/proc/package_scripts.mjs +31 -0
- package/scripts/utils/proc/parallel.mjs +25 -0
- package/scripts/utils/proc/pids.mjs +11 -0
- package/scripts/utils/{pm.mjs → proc/pm.mjs} +128 -158
- package/scripts/utils/{proc.mjs → proc/proc.mjs} +77 -2
- package/scripts/utils/review/base_ref.mjs +74 -0
- package/scripts/utils/review/base_ref.test.mjs +54 -0
- package/scripts/utils/review/runners/coderabbit.mjs +19 -0
- package/scripts/utils/review/runners/codex.mjs +51 -0
- package/scripts/utils/review/targets.mjs +24 -0
- package/scripts/utils/review/targets.test.mjs +36 -0
- package/scripts/utils/sandbox/review_pr_sandbox.mjs +106 -0
- package/scripts/utils/{happy_server_infra.mjs → server/infra/happy_server_infra.mjs} +10 -49
- package/scripts/utils/server/mobile_api_url.mjs +61 -0
- package/scripts/utils/server/mobile_api_url.test.mjs +41 -0
- package/scripts/utils/server/port.mjs +68 -0
- package/scripts/utils/{server.mjs → server/server.mjs} +12 -0
- package/scripts/utils/server/urls.mjs +101 -0
- package/scripts/utils/server/validate.mjs +88 -0
- package/scripts/utils/service/autostart_darwin.mjs +182 -0
- package/scripts/utils/service/autostart_darwin.test.mjs +50 -0
- package/scripts/utils/stack/context.mjs +23 -0
- package/scripts/utils/stack/dirs.mjs +27 -0
- package/scripts/utils/stack/editor_workspace.mjs +152 -0
- package/scripts/utils/stack/names.mjs +12 -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 +45 -0
- package/scripts/utils/{stack_startup.mjs → stack/startup.mjs} +9 -2
- package/scripts/utils/{stack_stop.mjs → stack/stop.mjs} +24 -19
- package/scripts/utils/stack_context.mjs +3 -3
- package/scripts/utils/stack_runtime_state.mjs +1 -1
- package/scripts/utils/stacks.mjs +2 -2
- package/scripts/utils/{browser.mjs → ui/browser.mjs} +1 -1
- package/scripts/utils/ui/qr.mjs +17 -0
- package/scripts/utils/ui/text.mjs +16 -0
- package/scripts/utils/validate.mjs +1 -1
- package/scripts/where.mjs +6 -6
- package/scripts/worktrees.mjs +171 -113
- package/scripts/utils/auth_sources.mjs +0 -12
- package/scripts/utils/dev_expo_web.mjs +0 -112
- package/scripts/utils/localhost_host.mjs +0 -17
- package/scripts/utils/server_port.mjs +0 -9
- package/scripts/utils/server_urls.mjs +0 -54
- /package/scripts/utils/{sandbox.mjs → env/sandbox.mjs} +0 -0
- /package/scripts/utils/{fs.mjs → fs/fs.mjs} +0 -0
- /package/scripts/utils/{canonical_home.mjs → paths/canonical_home.mjs} +0 -0
- /package/scripts/utils/{watch.mjs → proc/watch.mjs} +0 -0
package/README.md
CHANGED
|
@@ -23,13 +23,6 @@ Recommended:
|
|
|
23
23
|
npx happy-stacks setup --profile=selfhost
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
Alternative (global install):
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
npm install -g happy-stacks
|
|
30
|
-
happys setup --profile=selfhost
|
|
31
|
-
```
|
|
32
|
-
|
|
33
26
|
`setup` can optionally start Happy and guide you through authentication.
|
|
34
27
|
|
|
35
28
|
### Step 2: Start Happy
|
|
@@ -157,6 +150,7 @@ More details + automation: `[docs/remote-access.md](docs/remote-access.md)`.
|
|
|
157
150
|
- **Scripts**: `scripts/*.mjs` (bootstrap/dev/start/build/stacks/worktrees/service/tailscale/mobile)
|
|
158
151
|
- **Components**: `components/*` (each is its own Git repo)
|
|
159
152
|
- **Worktrees**: `components/.worktrees/<component>/<owner>/<branch...>`
|
|
153
|
+
- **CWD-scoped commands**: if you run `happys test/typecheck/lint` from inside a component checkout/worktree and omit components, it runs just that component; `happys build/dev/start` also prefer the checkout you’re currently inside.
|
|
160
154
|
|
|
161
155
|
Components:
|
|
162
156
|
|
|
@@ -192,7 +186,10 @@ happys wt pr happy https://github.com/slopus/happy/pull/123 --use
|
|
|
192
186
|
happys wt pr happy 123 --update --stash
|
|
193
187
|
```
|
|
194
188
|
|
|
195
|
-
|
|
189
|
+
##### Developer quickstart: create a PR stack (isolated ports/dirs; idempotent updates)
|
|
190
|
+
|
|
191
|
+
This creates (or reuses) a named stack, checks out PR worktrees for the selected components, optionally seeds auth, and starts the stack.
|
|
192
|
+
Re-run with `--reuse` to update the existing worktrees when the PR changes.
|
|
196
193
|
|
|
197
194
|
```bash
|
|
198
195
|
happys stack pr pr123 \
|
|
@@ -202,15 +199,33 @@ happys stack pr pr123 \
|
|
|
202
199
|
--dev
|
|
203
200
|
```
|
|
204
201
|
|
|
205
|
-
|
|
202
|
+
Optional: enable Expo dev-client for mobile reviewers (reuses the same Expo dev server; no second Metro process):
|
|
206
203
|
|
|
207
204
|
```bash
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
205
|
+
happys stack pr pr123 --happy=123 --happy-cli=456 --dev --mobile
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Optional: run it in a self-contained sandbox folder (delete it to uninstall completely):
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
SANDBOX="$(mktemp -d /tmp/happy-stacks-sandbox.XXXXXX)"
|
|
212
|
+
happys --sandbox-dir "$SANDBOX" stack pr pr123 --happy=123 --happy-cli=456 --dev
|
|
213
|
+
rm -rf "$SANDBOX"
|
|
211
214
|
```
|
|
212
215
|
|
|
213
|
-
|
|
216
|
+
Update when the PR changes:
|
|
217
|
+
|
|
218
|
+
- Re-run with `--reuse` to fast-forward worktrees when possible.
|
|
219
|
+
- If the PR was force-pushed, add `--force`.
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
happys stack pr pr123 --happy=123 --happy-cli=456 --reuse
|
|
223
|
+
happys stack pr pr123 --happy=123 --happy-cli=456 --reuse --force
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
##### Maintainer quickstart: one-shot “install + run PR stack” (idempotent)
|
|
227
|
+
|
|
228
|
+
This is the maintainer-friendly entrypoint. It is safe to re-run and will keep the PR stack wiring intact.
|
|
214
229
|
|
|
215
230
|
```bash
|
|
216
231
|
npx happy-stacks setup-pr \
|
|
@@ -218,11 +233,40 @@ npx happy-stacks setup-pr \
|
|
|
218
233
|
--happy-cli=https://github.com/slopus/happy-cli/pull/456
|
|
219
234
|
```
|
|
220
235
|
|
|
221
|
-
|
|
236
|
+
Optional: enable Expo dev-client for mobile reviewers (works with both default `--dev` and `--start`):
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
npx happy-stacks setup-pr --happy=123 --happy-cli=456 --mobile
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Optional: run it in a self-contained sandbox folder (auto-cleaned):
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
npx happy-stacks review-pr --happy=123 --happy-cli=456
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Short form (PR numbers):
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
npx happy-stacks setup-pr --happy=123 --happy-cli=456
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Override stack name (optional):
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
npx happy-stacks setup-pr --name=pr123 --happy=123 --happy-cli=456
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Update when the PR changes:
|
|
222
261
|
|
|
223
262
|
- Re-run the same command to fast-forward the PR worktrees.
|
|
224
263
|
- If the PR was force-pushed, add `--force`.
|
|
225
264
|
|
|
265
|
+
```bash
|
|
266
|
+
npx happy-stacks setup-pr --happy=123 --happy-cli=456
|
|
267
|
+
npx happy-stacks setup-pr --happy=123 --happy-cli=456 --force
|
|
268
|
+
```
|
|
269
|
+
|
|
226
270
|
Details: `[docs/worktrees-and-forks.md](docs/worktrees-and-forks.md)`.
|
|
227
271
|
|
|
228
272
|
#### Server flavor (server-light vs full server)
|
|
@@ -283,12 +327,20 @@ Details: `[docs/menubar.md](docs/menubar.md)`.
|
|
|
283
327
|
#### Mobile iOS dev (optional)
|
|
284
328
|
|
|
285
329
|
```bash
|
|
286
|
-
|
|
287
|
-
happys mobile --
|
|
330
|
+
# Install the shared "Happy Stacks Dev" dev-client app on your iPhone:
|
|
331
|
+
happys mobile-dev-client --install
|
|
332
|
+
|
|
333
|
+
# Install an isolated per-stack app (Release config, unique bundle id + scheme):
|
|
334
|
+
happys stack mobile:install <stack> --name="Happy (<stack>)"
|
|
288
335
|
```
|
|
289
336
|
|
|
290
337
|
Details: `[docs/mobile-ios.md](docs/mobile-ios.md)`.
|
|
291
338
|
|
|
339
|
+
#### Reviewing PRs in an isolated sandbox
|
|
340
|
+
|
|
341
|
+
- **Unique hostname per run (default)**: `happys review-pr` generates a unique stack name by default, which results in a unique `happy-<stack>.localhost` hostname. This prevents browser storage collisions when the sandbox is deleted between runs.
|
|
342
|
+
- **Reuse an existing sandbox**: if a previous run preserved a sandbox (e.g. `--keep-sandbox` or a failure in verbose mode), re-running `happys review-pr` offers an interactive choice to reuse it (keeping the same hostname + on-disk auth), or create a fresh sandbox.
|
|
343
|
+
|
|
292
344
|
#### Tauri desktop app (optional)
|
|
293
345
|
|
|
294
346
|
```bash
|
|
@@ -305,7 +357,7 @@ Details: `[docs/tauri.md](docs/tauri.md)`.
|
|
|
305
357
|
- (advanced) `happys bootstrap --interactive` (component installer wizard)
|
|
306
358
|
- **Run**:
|
|
307
359
|
- `happys start` (production-like; serves built UI via server-light)
|
|
308
|
-
- `happys dev` (dev; Expo
|
|
360
|
+
- `happys dev` (dev; Expo dev server for UI, optional dev-client via `--mobile`)
|
|
309
361
|
- **Server flavor**:
|
|
310
362
|
- `happys srv status`
|
|
311
363
|
- `happys srv use --interactive`
|
|
@@ -319,7 +371,10 @@ Details: `[docs/tauri.md](docs/tauri.md)`.
|
|
|
319
371
|
- `happys stack dev <name>` / `happys stack start <name>`
|
|
320
372
|
- `happys stack edit <name> --interactive`
|
|
321
373
|
- `happys stack wt <name> -- use --interactive`
|
|
374
|
+
- `happys stack review <name> [component...] [--reviewers=coderabbit,codex] [--base-ref=<ref>]`
|
|
322
375
|
- `happys stack migrate`
|
|
376
|
+
- **Reviews (local diff review)**:
|
|
377
|
+
- `happys review [component...] [--reviewers=coderabbit,codex] [--base-remote=<remote>] [--base-branch=<branch>] [--base-ref=<ref>]`
|
|
323
378
|
- **Menu bar (SwiftBar)**:
|
|
324
379
|
- `happys menubar install`
|
|
325
380
|
|
|
@@ -352,19 +407,23 @@ Notes:
|
|
|
352
407
|
|
|
353
408
|
### Sandbox / test installs (fully isolated)
|
|
354
409
|
|
|
355
|
-
If you want to test the full setup flow (including PR stacks) without impacting your “real” install, run with
|
|
410
|
+
If you want to test the full setup flow (including PR stacks) without impacting your “real” install, run everything with `--sandbox-dir`.
|
|
411
|
+
To fully uninstall the test run, stop the sandbox stacks and delete the sandbox folder.
|
|
356
412
|
|
|
357
413
|
```bash
|
|
358
|
-
|
|
359
|
-
```
|
|
414
|
+
SANDBOX="$(mktemp -d /tmp/happy-stacks-sandbox.XXXXXX)"
|
|
360
415
|
|
|
361
|
-
|
|
416
|
+
# Run a PR stack (fully isolated install)
|
|
417
|
+
npx happy-stacks --sandbox-dir "$SANDBOX" setup-pr --happy=123 --happy-cli=456
|
|
362
418
|
|
|
363
|
-
|
|
364
|
-
|
|
419
|
+
# Tear down + uninstall
|
|
420
|
+
npx happy-stacks --sandbox-dir "$SANDBOX" stop --yes --no-service
|
|
421
|
+
rm -rf "$SANDBOX"
|
|
365
422
|
```
|
|
366
423
|
|
|
367
|
-
|
|
368
|
-
|
|
424
|
+
Notes:
|
|
425
|
+
|
|
426
|
+
- Sandbox mode disables global OS side effects (**PATH edits**, **SwiftBar plugin install**, **LaunchAgents/systemd services**, **Tailscale Serve enable/disable**) by default.
|
|
427
|
+
- To explicitly allow those for testing, set `HAPPY_STACKS_SANDBOX_ALLOW_GLOBAL=1` (still recommended to clean up after).
|
|
369
428
|
|
|
370
429
|
For contributor/LLM workflow expectations: `[AGENTS.md](AGENTS.md)`.
|
package/bin/happys.mjs
CHANGED
|
@@ -8,13 +8,13 @@ import { homedir } from 'node:os';
|
|
|
8
8
|
import { dirname, join } from 'node:path';
|
|
9
9
|
import { fileURLToPath } from 'node:url';
|
|
10
10
|
import { commandHelpArgs, renderHappysRootHelp, resolveHappysCommand } from '../scripts/utils/cli/cli_registry.mjs';
|
|
11
|
-
import { expandHome, getCanonicalHomeEnvPathFromEnv } from '../scripts/utils/canonical_home.mjs';
|
|
11
|
+
import { expandHome, getCanonicalHomeEnvPathFromEnv } from '../scripts/utils/paths/canonical_home.mjs';
|
|
12
12
|
|
|
13
13
|
function getCliRootDir() {
|
|
14
14
|
return dirname(dirname(fileURLToPath(import.meta.url)));
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
// expandHome is imported from scripts/utils/canonical_home.mjs
|
|
17
|
+
// expandHome is imported from scripts/utils/paths/canonical_home.mjs
|
|
18
18
|
|
|
19
19
|
function dotenvGetQuick(envPath, key) {
|
|
20
20
|
try {
|
|
@@ -110,6 +110,42 @@ function stripGlobalOpt(argv, { name, aliases = [] }) {
|
|
|
110
110
|
return { value: '', argv };
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
function applyVerbosityIfRequested(argv) {
|
|
114
|
+
// Global verbosity:
|
|
115
|
+
// - supports -v/-vv/-vvv anywhere before/after the command
|
|
116
|
+
// - supports --verbose and --verbose=N
|
|
117
|
+
//
|
|
118
|
+
// We set HAPPY_STACKS_VERBOSE (0-3) and strip these args so downstream scripts don't need to support them.
|
|
119
|
+
let level = Number.isFinite(Number(process.env.HAPPY_STACKS_VERBOSE)) ? Number(process.env.HAPPY_STACKS_VERBOSE) : null;
|
|
120
|
+
let next = [];
|
|
121
|
+
for (const a of argv) {
|
|
122
|
+
if (a === '-v' || a === '-vv' || a === '-vvv') {
|
|
123
|
+
const n = a.length - 1;
|
|
124
|
+
level = Math.max(level ?? 0, n);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (a === '--verbose') {
|
|
128
|
+
level = Math.max(level ?? 0, 1);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (a.startsWith('--verbose=')) {
|
|
132
|
+
const raw = a.slice('--verbose='.length).trim();
|
|
133
|
+
const n = Number(raw);
|
|
134
|
+
if (Number.isFinite(n)) {
|
|
135
|
+
level = Math.max(level ?? 0, Math.max(0, Math.min(3, Math.floor(n))));
|
|
136
|
+
} else {
|
|
137
|
+
level = Math.max(level ?? 0, 1);
|
|
138
|
+
}
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
next.push(a);
|
|
142
|
+
}
|
|
143
|
+
if (level != null) {
|
|
144
|
+
process.env.HAPPY_STACKS_VERBOSE = String(Math.max(0, Math.min(3, Math.floor(level))));
|
|
145
|
+
}
|
|
146
|
+
return next;
|
|
147
|
+
}
|
|
148
|
+
|
|
113
149
|
function applySandboxDirIfRequested(argv) {
|
|
114
150
|
const explicit = (process.env.HAPPY_STACKS_SANDBOX_DIR ?? '').trim();
|
|
115
151
|
const { value, argv: nextArgv } = stripGlobalOpt(argv, { name: '--sandbox-dir', aliases: ['--sandbox'] });
|
|
@@ -117,6 +153,8 @@ function applySandboxDirIfRequested(argv) {
|
|
|
117
153
|
if (!raw) return { argv: nextArgv, enabled: false };
|
|
118
154
|
|
|
119
155
|
const sandboxDir = expandHome(raw);
|
|
156
|
+
const allowGlobalRaw = (process.env.HAPPY_STACKS_SANDBOX_ALLOW_GLOBAL ?? '').trim().toLowerCase();
|
|
157
|
+
const allowGlobal = allowGlobalRaw === '1' || allowGlobalRaw === 'true' || allowGlobalRaw === 'yes' || allowGlobalRaw === 'y';
|
|
120
158
|
// Keep all state under one folder that can be deleted to reset completely.
|
|
121
159
|
const canonicalHomeDir = join(sandboxDir, 'canonical');
|
|
122
160
|
const homeDir = join(sandboxDir, 'home');
|
|
@@ -124,23 +162,76 @@ function applySandboxDirIfRequested(argv) {
|
|
|
124
162
|
const runtimeDir = join(sandboxDir, 'runtime');
|
|
125
163
|
const storageDir = join(sandboxDir, 'storage');
|
|
126
164
|
|
|
165
|
+
// Sandbox isolation MUST win over any pre-exported Happy Stacks env vars.
|
|
166
|
+
// Otherwise sandbox runs can accidentally read/write "real" machine state.
|
|
167
|
+
//
|
|
168
|
+
// Keep only a tiny set of sandbox-safe globals; everything else should be driven by flags
|
|
169
|
+
// and stack env files inside the sandbox.
|
|
170
|
+
const preserved = new Map();
|
|
171
|
+
const keepKeys = [
|
|
172
|
+
'HAPPY_STACKS_VERBOSE',
|
|
173
|
+
'HAPPY_STACKS_INVOKED_CWD',
|
|
174
|
+
'HAPPY_STACKS_SANDBOX_DIR',
|
|
175
|
+
'HAPPY_STACKS_SANDBOX_ALLOW_GLOBAL',
|
|
176
|
+
'HAPPY_STACKS_UPDATE_CHECK',
|
|
177
|
+
'HAPPY_STACKS_UPDATE_CHECK_INTERVAL_MS',
|
|
178
|
+
'HAPPY_STACKS_UPDATE_NOTIFY_INTERVAL_MS',
|
|
179
|
+
];
|
|
180
|
+
for (const k of keepKeys) {
|
|
181
|
+
if (process.env[k] != null && String(process.env[k]).trim() !== '') {
|
|
182
|
+
preserved.set(k, process.env[k]);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
for (const k of Object.keys(process.env)) {
|
|
186
|
+
if (k.startsWith('HAPPY_STACKS_') || k.startsWith('HAPPY_LOCAL_')) {
|
|
187
|
+
delete process.env[k];
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
// Also clear unprefixed Happy vars; sandbox commands should compute these from stack state.
|
|
191
|
+
if (k === 'HAPPY_HOME_DIR' || k === 'HAPPY_SERVER_URL' || k === 'HAPPY_WEBAPP_URL') {
|
|
192
|
+
delete process.env[k];
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
for (const [k, v] of preserved.entries()) {
|
|
196
|
+
process.env[k] = v;
|
|
197
|
+
}
|
|
198
|
+
|
|
127
199
|
process.env.HAPPY_STACKS_SANDBOX_DIR = sandboxDir;
|
|
128
200
|
process.env.HAPPY_STACKS_CLI_ROOT_DISABLE = '1'; // never re-exec into a user's "real" install when sandboxing
|
|
129
201
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
process.env.
|
|
134
|
-
process.env.
|
|
135
|
-
|
|
136
|
-
process.env.
|
|
137
|
-
process.env.
|
|
138
|
-
|
|
139
|
-
process.env.
|
|
140
|
-
process.env.
|
|
141
|
-
|
|
142
|
-
process.env.
|
|
143
|
-
process.env.
|
|
202
|
+
// In sandbox mode, we MUST force all state directories into the sandbox, even if the user
|
|
203
|
+
// exported HAPPY_STACKS_* in their shell. Otherwise sandbox runs can accidentally read/write
|
|
204
|
+
// "real" machine state (breaking isolation).
|
|
205
|
+
process.env.HAPPY_STACKS_CANONICAL_HOME_DIR = canonicalHomeDir;
|
|
206
|
+
process.env.HAPPY_LOCAL_CANONICAL_HOME_DIR = canonicalHomeDir;
|
|
207
|
+
|
|
208
|
+
process.env.HAPPY_STACKS_HOME_DIR = homeDir;
|
|
209
|
+
process.env.HAPPY_LOCAL_HOME_DIR = homeDir;
|
|
210
|
+
|
|
211
|
+
process.env.HAPPY_STACKS_WORKSPACE_DIR = workspaceDir;
|
|
212
|
+
process.env.HAPPY_LOCAL_WORKSPACE_DIR = workspaceDir;
|
|
213
|
+
|
|
214
|
+
process.env.HAPPY_STACKS_RUNTIME_DIR = runtimeDir;
|
|
215
|
+
process.env.HAPPY_LOCAL_RUNTIME_DIR = runtimeDir;
|
|
216
|
+
|
|
217
|
+
process.env.HAPPY_STACKS_STORAGE_DIR = storageDir;
|
|
218
|
+
process.env.HAPPY_LOCAL_STORAGE_DIR = storageDir;
|
|
219
|
+
|
|
220
|
+
// Sandbox default: disallow global side effects unless explicitly opted in.
|
|
221
|
+
// This keeps sandbox runs fast, deterministic, and isolated.
|
|
222
|
+
if (!allowGlobal) {
|
|
223
|
+
// Network-y UX (background update checks) are not useful in a temporary sandbox.
|
|
224
|
+
process.env.HAPPY_STACKS_UPDATE_CHECK = '0';
|
|
225
|
+
process.env.HAPPY_STACKS_UPDATE_CHECK_INTERVAL_MS = '0';
|
|
226
|
+
process.env.HAPPY_STACKS_UPDATE_NOTIFY_INTERVAL_MS = '0';
|
|
227
|
+
|
|
228
|
+
// Never auto-enable or reset Tailscale Serve in sandbox.
|
|
229
|
+
// (Tailscale is global machine state; sandbox runs must not touch it.)
|
|
230
|
+
process.env.HAPPY_LOCAL_TAILSCALE_SERVE = '0';
|
|
231
|
+
process.env.HAPPY_STACKS_TAILSCALE_SERVE = '0';
|
|
232
|
+
process.env.HAPPY_LOCAL_TAILSCALE_RESET_ON_EXIT = '0';
|
|
233
|
+
process.env.HAPPY_STACKS_TAILSCALE_RESET_ON_EXIT = '0';
|
|
234
|
+
}
|
|
144
235
|
|
|
145
236
|
return { argv: nextArgv, enabled: true };
|
|
146
237
|
}
|
|
@@ -248,8 +339,16 @@ function runNodeScript(cliRootDir, scriptRelPath, args) {
|
|
|
248
339
|
function main() {
|
|
249
340
|
const cliRootDir = getCliRootDir();
|
|
250
341
|
const initialArgv = process.argv.slice(2);
|
|
251
|
-
const
|
|
342
|
+
const argv0 = applyVerbosityIfRequested(initialArgv);
|
|
343
|
+
const { argv, enabled: sandboxed } = applySandboxDirIfRequested(argv0);
|
|
252
344
|
void sandboxed;
|
|
345
|
+
|
|
346
|
+
// Preserve the original working directory across re-exec to the CLI root so commands can infer
|
|
347
|
+
// component/worktree context even when the actual scripts run with cwd=cliRootDir.
|
|
348
|
+
if (!(process.env.HAPPY_STACKS_INVOKED_CWD ?? '').trim()) {
|
|
349
|
+
process.env.HAPPY_STACKS_INVOKED_CWD = process.cwd();
|
|
350
|
+
}
|
|
351
|
+
|
|
253
352
|
maybeReexecToCliRoot(cliRootDir);
|
|
254
353
|
|
|
255
354
|
// If the user passed only flags (common via `npx happy-stacks --help`),
|
|
@@ -559,9 +559,9 @@ Most commands support `--help` and `--json`.
|
|
|
559
559
|
### Core run commands
|
|
560
560
|
|
|
561
561
|
- **`happys start`**: production-like run (no Expo)
|
|
562
|
-
- Flags: `--server=happy-server|happy-server-light`, `--restart`, `--no-daemon`, `--no-ui`, `--no-browser`
|
|
562
|
+
- Flags: `--server=happy-server|happy-server-light`, `--restart`, `--no-daemon`, `--no-ui`, `--no-browser`, `--mobile`
|
|
563
563
|
- **`happys dev`**: dev run (server + daemon + Expo web)
|
|
564
|
-
- Flags: `--server=happy-server|happy-server-light`, `--restart`, `--no-daemon`, `--no-ui`, `--watch`, `--no-watch`, `--no-browser`
|
|
564
|
+
- Flags: `--server=happy-server|happy-server-light`, `--restart`, `--no-daemon`, `--no-ui`, `--watch`, `--no-watch`, `--no-browser`, `--mobile`
|
|
565
565
|
- **`happys stop`**: stop stacks and related processes
|
|
566
566
|
- Flags: `--except-stacks=main,exp1`, `--yes`, `--aggressive`, `--sweep-owned`, `--no-docker`, `--no-service`
|
|
567
567
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Isolated Linux VM (Apple Silicon) for `review-pr`
|
|
2
|
+
|
|
3
|
+
If you want to validate `happys review-pr` on a **fresh system** (no existing `~/.happy-stacks`, no host tooling), the simplest repeatable approach on Apple Silicon is a Linux VM managed by **Lima** (it uses Apple’s Virtualization.framework).
|
|
4
|
+
|
|
5
|
+
This avoids Docker/container UX issues (browser opening, Expo networking, file watching) while still being truly “clean”.
|
|
6
|
+
|
|
7
|
+
## Option A (recommended): Lima + Ubuntu ARM64
|
|
8
|
+
|
|
9
|
+
### 1) Install Lima (macOS host)
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
brew install lima
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### 2) Create a VM
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
limactl create --name happy-pr --tty=false template://ubuntu-24.04
|
|
19
|
+
limactl start happy-pr
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### 3) Provision the VM (Node + build deps)
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
limactl shell happy-pr
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Inside the VM:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
curl -fsSL https://raw.githubusercontent.com/leeroybrun/happy-local/main/scripts/provision/linux-ubuntu-review-pr.sh -o /tmp/linux-ubuntu-review-pr.sh && chmod +x /tmp/linux-ubuntu-review-pr.sh && /tmp/linux-ubuntu-review-pr.sh
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 4) Run `review-pr` via `npx` (published package)
|
|
35
|
+
|
|
36
|
+
Inside the VM:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx --yes happy-stacks@latest review-pr \
|
|
40
|
+
--happy=https://github.com/leeroybrun/happy/pull/10 \
|
|
41
|
+
--happy-cli=https://github.com/leeroybrun/happy-cli/pull/12 \
|
|
42
|
+
--no-mobile \
|
|
43
|
+
--verbose
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Notes:
|
|
47
|
+
- `--no-mobile` keeps the validation focused (Expo mobile dev-client adds more host requirements).
|
|
48
|
+
- You can also add `--keep-sandbox` if you want to inspect the sandbox contents after a failure.
|
|
49
|
+
- For full reproducibility, pin the version: `npx --yes happy-stacks@0.3.0 review-pr ...`
|
|
50
|
+
|
|
51
|
+
### Optional: test **unreleased local changes**
|
|
52
|
+
|
|
53
|
+
If you need to test changes that aren’t published to npm yet:
|
|
54
|
+
|
|
55
|
+
1) On your Mac (repo checkout):
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npm pack
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
2) Copy the generated `happy-stacks-*.tgz` into the VM (any method you like), then inside the VM:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npx --yes ./happy-stacks-*.tgz review-pr ...
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Option B: GUI VM (UTM) – simplest when you want a “real desktop”
|
|
68
|
+
|
|
69
|
+
If you want the most realistic “reviewer” experience (open browser, etc.), a GUI VM is great:
|
|
70
|
+
|
|
71
|
+
1. Install UTM (macOS host): `brew install --cask utm`
|
|
72
|
+
2. Create an Ubuntu 24.04 ARM64 VM (UTM wizard).
|
|
73
|
+
3. Run the same provisioning + `node bin/happys.mjs review-pr ...` inside the VM.
|
|
74
|
+
|
|
75
|
+
## Option C: Apple “container” / Docker
|
|
76
|
+
|
|
77
|
+
Containers are excellent for server-only validation, but are usually **not** the best fit for end-to-end `review-pr` UX because:
|
|
78
|
+
- opening the host browser from inside the container is awkward
|
|
79
|
+
- Expo/dev-server workflows and networking tend to require extra port mapping and host interaction
|
|
80
|
+
|
|
81
|
+
Use containers only if you explicitly want “CLI-only” checks and are okay opening URLs manually.
|
|
82
|
+
|