happy-stacks 0.3.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 +93 -40
- package/bin/happys.mjs +158 -16
- 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 +3 -4
- package/docs/isolated-linux-vm.md +82 -0
- package/docs/mobile-ios.md +112 -54
- package/docs/monorepo-migration.md +286 -0
- package/docs/server-flavors.md +19 -3
- package/docs/stacks.md +35 -0
- package/package.json +5 -1
- package/scripts/auth.mjs +32 -10
- package/scripts/build.mjs +55 -8
- package/scripts/daemon.mjs +166 -10
- package/scripts/dev.mjs +198 -50
- package/scripts/doctor.mjs +0 -4
- package/scripts/edison.mjs +6 -4
- package/scripts/env.mjs +150 -0
- package/scripts/env_cmd.test.mjs +128 -0
- package/scripts/init.mjs +8 -3
- package/scripts/install.mjs +207 -69
- package/scripts/lint.mjs +24 -4
- package/scripts/migrate.mjs +3 -12
- package/scripts/mobile.mjs +88 -104
- package/scripts/mobile_dev_client.mjs +83 -0
- package/scripts/monorepo.mjs +1096 -0
- package/scripts/monorepo_port.test.mjs +1470 -0
- package/scripts/provision/linux-ubuntu-review-pr.sh +51 -0
- package/scripts/review.mjs +908 -0
- package/scripts/review_pr.mjs +353 -0
- package/scripts/run.mjs +101 -21
- package/scripts/service.mjs +2 -2
- package/scripts/setup.mjs +189 -68
- package/scripts/setup_pr.mjs +586 -38
- package/scripts/stack.mjs +990 -196
- 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/tailscale.mjs +37 -1
- package/scripts/test.mjs +45 -8
- package/scripts/tui.mjs +395 -39
- package/scripts/typecheck.mjs +24 -4
- package/scripts/utils/auth/daemon_gate.mjs +55 -0
- package/scripts/utils/auth/daemon_gate.test.mjs +37 -0
- 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/interactive_stack_auth.mjs +72 -0
- package/scripts/utils/auth/login_ux.mjs +32 -13
- package/scripts/utils/auth/sources.mjs +26 -0
- package/scripts/utils/auth/stack_guided_login.mjs +353 -0
- package/scripts/utils/cli/cli_registry.mjs +43 -4
- package/scripts/utils/cli/cwd_scope.mjs +136 -0
- package/scripts/utils/cli/cwd_scope.test.mjs +110 -0
- package/scripts/utils/cli/log_forwarder.mjs +157 -0
- package/scripts/utils/cli/prereqs.mjs +75 -0
- package/scripts/utils/cli/prereqs.test.mjs +34 -0
- package/scripts/utils/cli/progress.mjs +126 -0
- package/scripts/utils/cli/verbosity.mjs +12 -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 +61 -4
- package/scripts/utils/dev/expo_dev.mjs +430 -0
- package/scripts/utils/dev/expo_dev.test.mjs +76 -0
- package/scripts/utils/dev/server.mjs +36 -42
- package/scripts/utils/dev_auth_key.mjs +169 -0
- 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/expo/command.mjs +52 -0
- package/scripts/utils/expo/expo.mjs +20 -1
- package/scripts/utils/expo/metro_ports.mjs +114 -0
- package/scripts/utils/git/git.mjs +67 -0
- package/scripts/utils/git/worktrees.mjs +80 -25
- package/scripts/utils/git/worktrees_monorepo.test.mjs +54 -0
- package/scripts/utils/handy_master_secret.mjs +94 -0
- 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/lan_ip.mjs +24 -0
- package/scripts/utils/net/ports.mjs +9 -1
- package/scripts/utils/net/tcp_forward.mjs +162 -0
- package/scripts/utils/net/url.mjs +30 -0
- package/scripts/utils/net/url.test.mjs +20 -0
- package/scripts/utils/paths/localhost_host.mjs +50 -3
- package/scripts/utils/paths/paths.mjs +159 -40
- 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/parallel.mjs +25 -0
- package/scripts/utils/proc/pm.mjs +176 -22
- 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 +136 -4
- package/scripts/utils/proc/proc.test.mjs +77 -0
- package/scripts/utils/review/base_ref.mjs +74 -0
- package/scripts/utils/review/base_ref.test.mjs +54 -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 +61 -0
- package/scripts/utils/review/runners/coderabbit.test.mjs +59 -0
- package/scripts/utils/review/runners/codex.mjs +61 -0
- 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/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/server/flavor_scripts.mjs +98 -0
- package/scripts/utils/server/flavor_scripts.test.mjs +146 -0
- 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/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/urls.mjs +14 -4
- package/scripts/utils/server/validate.mjs +53 -16
- package/scripts/utils/server/validate.test.mjs +89 -0
- package/scripts/utils/service/autostart_darwin.mjs +42 -2
- package/scripts/utils/service/autostart_darwin.test.mjs +50 -0
- package/scripts/utils/stack/context.mjs +2 -2
- package/scripts/utils/stack/editor_workspace.mjs +6 -6
- package/scripts/utils/stack/interactive_stack_config.mjs +185 -0
- package/scripts/utils/stack/pr_stack_name.mjs +16 -0
- package/scripts/utils/stack/runtime_state.mjs +2 -1
- package/scripts/utils/stack/startup.mjs +120 -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/stack/stop.mjs +15 -4
- package/scripts/utils/stack_context.mjs +23 -0
- package/scripts/utils/stack_runtime_state.mjs +104 -0
- package/scripts/utils/stacks.mjs +38 -0
- package/scripts/utils/tailscale/ip.mjs +116 -0
- package/scripts/utils/ui/ansi.mjs +39 -0
- package/scripts/utils/ui/qr.mjs +17 -0
- package/scripts/utils/validate.mjs +88 -0
- package/scripts/where.mjs +2 -2
- package/scripts/worktrees.mjs +755 -179
- 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
- package/scripts/utils/dev/expo_web.mjs +0 -112
package/scripts/setup.mjs
CHANGED
|
@@ -4,8 +4,8 @@ import { existsSync } from 'node:fs';
|
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { parseArgs } from './utils/cli/args.mjs';
|
|
6
6
|
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
|
|
7
|
-
import { getRootDir, resolveStackEnvPath } from './utils/paths/paths.mjs';
|
|
8
|
-
import { isTty, promptSelect, withRl } from './utils/cli/wizard.mjs';
|
|
7
|
+
import { getHappyStacksHomeDir, getRootDir, resolveStackEnvPath } from './utils/paths/paths.mjs';
|
|
8
|
+
import { isTty, prompt, promptSelect, withRl } from './utils/cli/wizard.mjs';
|
|
9
9
|
import { getCanonicalHomeDir } from './utils/env/config.mjs';
|
|
10
10
|
import { ensureEnvLocalUpdated } from './utils/env/env_local.mjs';
|
|
11
11
|
import { run, runCapture } from './utils/proc/proc.mjs';
|
|
@@ -23,6 +23,44 @@ import { openUrlInBrowser } from './utils/ui/browser.mjs';
|
|
|
23
23
|
import { commandExists } from './utils/proc/commands.mjs';
|
|
24
24
|
import { readEnvValueFromFile } from './utils/env/read.mjs';
|
|
25
25
|
import { readServerPortFromEnvFile, resolveServerPortFromEnv } from './utils/server/port.mjs';
|
|
26
|
+
import { guidedStackWebSignupThenLogin } from './utils/auth/guided_stack_web_login.mjs';
|
|
27
|
+
import { getVerbosityLevel } from './utils/cli/verbosity.mjs';
|
|
28
|
+
import { runCommandLogged } from './utils/cli/progress.mjs';
|
|
29
|
+
import { bold, cyan, dim, green } from './utils/ui/ansi.mjs';
|
|
30
|
+
import { expandHome } from './utils/paths/canonical_home.mjs';
|
|
31
|
+
|
|
32
|
+
function resolveWorkspaceDirDefault() {
|
|
33
|
+
const explicit = (process.env.HAPPY_STACKS_WORKSPACE_DIR ?? process.env.HAPPY_LOCAL_WORKSPACE_DIR ?? '').toString().trim();
|
|
34
|
+
if (explicit) return expandHome(explicit);
|
|
35
|
+
return join(getHappyStacksHomeDir(process.env), 'workspace');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function normalizeWorkspaceDirInput(raw, { homeDir }) {
|
|
39
|
+
const trimmed = String(raw ?? '').trim();
|
|
40
|
+
const expanded = expandHome(trimmed);
|
|
41
|
+
if (!expanded) return '';
|
|
42
|
+
// If relative, treat it as relative to the home dir (same rule as init.mjs).
|
|
43
|
+
return expanded.startsWith('/') ? expanded : join(homeDir, expanded);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function resolveMainWebappUrlForAuth({ rootDir, port }) {
|
|
47
|
+
try {
|
|
48
|
+
const raw = await runCapture(process.execPath, [join(rootDir, 'scripts', 'auth.mjs'), 'login', '--print', '--json'], {
|
|
49
|
+
cwd: rootDir,
|
|
50
|
+
env: {
|
|
51
|
+
...process.env,
|
|
52
|
+
HAPPY_STACKS_SERVER_PORT: String(port),
|
|
53
|
+
HAPPY_LOCAL_SERVER_PORT: String(port),
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
const parsed = JSON.parse(String(raw ?? '').trim());
|
|
57
|
+
const cmd = typeof parsed?.cmd === 'string' ? parsed.cmd : '';
|
|
58
|
+
const m = cmd.match(/HAPPY_WEBAPP_URL="([^"]+)"/);
|
|
59
|
+
return m?.[1] ? String(m[1]) : '';
|
|
60
|
+
} catch {
|
|
61
|
+
return '';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
26
64
|
|
|
27
65
|
async function resolveMainServerPort() {
|
|
28
66
|
// Priority:
|
|
@@ -39,14 +77,18 @@ async function resolveMainServerPort() {
|
|
|
39
77
|
}
|
|
40
78
|
|
|
41
79
|
async function ensureSetupConfigPersisted({ rootDir, profile, serverComponent, tailscaleWanted, menubarMode }) {
|
|
80
|
+
const repoSourceForProfile =
|
|
81
|
+
profile === 'selfhost' ? (serverComponent === 'happy-server-light' ? 'forks' : 'upstream') : null;
|
|
42
82
|
const updates = [
|
|
43
83
|
{ key: 'HAPPY_STACKS_SERVER_COMPONENT', value: serverComponent },
|
|
44
84
|
{ key: 'HAPPY_LOCAL_SERVER_COMPONENT', value: serverComponent },
|
|
45
|
-
// Default for selfhost:
|
|
46
|
-
|
|
85
|
+
// Default for selfhost:
|
|
86
|
+
// - full server: upstream (slopus/*)
|
|
87
|
+
// - server-light: forks (sqlite server-light is not available upstream today)
|
|
88
|
+
...(repoSourceForProfile
|
|
47
89
|
? [
|
|
48
|
-
{ key: 'HAPPY_STACKS_REPO_SOURCE', value:
|
|
49
|
-
{ key: 'HAPPY_LOCAL_REPO_SOURCE', value:
|
|
90
|
+
{ key: 'HAPPY_STACKS_REPO_SOURCE', value: repoSourceForProfile },
|
|
91
|
+
{ key: 'HAPPY_LOCAL_REPO_SOURCE', value: repoSourceForProfile },
|
|
50
92
|
]
|
|
51
93
|
: []),
|
|
52
94
|
{ key: 'HAPPY_STACKS_MENUBAR_MODE', value: menubarMode },
|
|
@@ -248,6 +290,7 @@ async function cmdSetup({ rootDir, argv }) {
|
|
|
248
290
|
flags: [
|
|
249
291
|
'--profile=selfhost|dev',
|
|
250
292
|
'--server=happy-server-light|happy-server',
|
|
293
|
+
'--workspace-dir=/absolute/path # dev profile only',
|
|
251
294
|
'--install-path',
|
|
252
295
|
'--start-now',
|
|
253
296
|
'--auth|--no-auth',
|
|
@@ -262,7 +305,8 @@ async function cmdSetup({ rootDir, argv }) {
|
|
|
262
305
|
' happys setup',
|
|
263
306
|
' happys setup --profile=selfhost',
|
|
264
307
|
' happys setup --profile=dev',
|
|
265
|
-
' happys setup
|
|
308
|
+
' happys setup --profile=dev --workspace-dir=~/Development/happy',
|
|
309
|
+
' happys setup pr --happy=<pr-url|number> [--happy-server-light=<pr-url|number>]',
|
|
266
310
|
' happys setup --auth',
|
|
267
311
|
' happys setup --no-auth',
|
|
268
312
|
'',
|
|
@@ -280,10 +324,10 @@ async function cmdSetup({ rootDir, argv }) {
|
|
|
280
324
|
if (!profile && interactive) {
|
|
281
325
|
profile = await withRl(async (rl) => {
|
|
282
326
|
return await promptSelect(rl, {
|
|
283
|
-
title: '
|
|
327
|
+
title: bold(`✨ ${cyan('Happy Stacks')} setup ✨\n\nWhat is your goal?`),
|
|
284
328
|
options: [
|
|
285
|
-
{ label: '
|
|
286
|
-
{ label: '
|
|
329
|
+
{ label: `${cyan('Self-host')}: use Happy on this machine`, value: 'selfhost' },
|
|
330
|
+
{ label: `${cyan('Development')}: worktrees + stacks + contributor workflows`, value: 'dev' },
|
|
287
331
|
],
|
|
288
332
|
defaultIndex: 0,
|
|
289
333
|
});
|
|
@@ -293,6 +337,71 @@ async function cmdSetup({ rootDir, argv }) {
|
|
|
293
337
|
profile = 'selfhost';
|
|
294
338
|
}
|
|
295
339
|
|
|
340
|
+
const verbosity = getVerbosityLevel(process.env);
|
|
341
|
+
const quietUi = interactive && verbosity === 0 && !json;
|
|
342
|
+
|
|
343
|
+
async function runNodeScriptMaybeQuiet({ label, rel, args = [], env = process.env }) {
|
|
344
|
+
if (!quietUi) {
|
|
345
|
+
await run(process.execPath, [join(rootDir, rel), ...args], { cwd: rootDir, env });
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
const baseLogDir = join(getHappyStacksHomeDir(process.env), 'logs', 'setup');
|
|
349
|
+
const logPath = join(baseLogDir, `${label.replace(/[^a-z0-9]+/gi, '-').toLowerCase()}.${Date.now()}.log`);
|
|
350
|
+
try {
|
|
351
|
+
await runCommandLogged({
|
|
352
|
+
label,
|
|
353
|
+
cmd: process.execPath,
|
|
354
|
+
args: [join(rootDir, rel), ...args],
|
|
355
|
+
cwd: rootDir,
|
|
356
|
+
env,
|
|
357
|
+
logPath,
|
|
358
|
+
quiet: true,
|
|
359
|
+
showSteps: true,
|
|
360
|
+
});
|
|
361
|
+
} catch (e) {
|
|
362
|
+
const lp = e?.logPath ? String(e.logPath) : logPath;
|
|
363
|
+
// eslint-disable-next-line no-console
|
|
364
|
+
console.error(`[setup] failed: ${label}`);
|
|
365
|
+
// eslint-disable-next-line no-console
|
|
366
|
+
console.error(`${dim('log:')} ${lp}`);
|
|
367
|
+
throw e;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function printProfileIntro({ profile }) {
|
|
372
|
+
if (!process.stdout.isTTY || json) return;
|
|
373
|
+
const header = profile === 'selfhost' ? `${cyan('Self-host')} setup` : `${cyan('Development')} setup`;
|
|
374
|
+
const lines = [
|
|
375
|
+
'',
|
|
376
|
+
bold(header),
|
|
377
|
+
profile === 'selfhost'
|
|
378
|
+
? dim('Run Happy locally (optionally with Tailscale + autostart).')
|
|
379
|
+
: dim('Prepare a contributor workspace (components + worktrees + stacks).'),
|
|
380
|
+
'',
|
|
381
|
+
bold('What will happen:'),
|
|
382
|
+
profile === 'selfhost'
|
|
383
|
+
? [
|
|
384
|
+
`- ${cyan('init')}: set up Happy Stacks home + shims`,
|
|
385
|
+
`- ${cyan('bootstrap')}: clone/install components`,
|
|
386
|
+
`- ${cyan('start')}: (optional) start Happy now`,
|
|
387
|
+
`- ${cyan('login')}: (optional) authenticate`,
|
|
388
|
+
]
|
|
389
|
+
: [
|
|
390
|
+
`- ${cyan('workspace')}: choose where components + worktrees live`,
|
|
391
|
+
`- ${cyan('init')}: set up Happy Stacks home + shims`,
|
|
392
|
+
`- ${cyan('bootstrap')}: clone/install components + dev tooling`,
|
|
393
|
+
`- ${cyan('stacks')}: (optional) create an isolated dev stack`,
|
|
394
|
+
],
|
|
395
|
+
'',
|
|
396
|
+
].flat();
|
|
397
|
+
// eslint-disable-next-line no-console
|
|
398
|
+
console.log(lines.join('\n'));
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (interactive) {
|
|
402
|
+
printProfileIntro({ profile });
|
|
403
|
+
}
|
|
404
|
+
|
|
296
405
|
const platform = process.platform;
|
|
297
406
|
const supportsAutostart = platform === 'darwin' || platform === 'linux';
|
|
298
407
|
const supportsMenubar = platform === 'darwin';
|
|
@@ -302,7 +411,7 @@ async function cmdSetup({ rootDir, argv }) {
|
|
|
302
411
|
if (profile === 'selfhost' && interactive && !serverFromArg) {
|
|
303
412
|
serverComponent = await withRl(async (rl) => {
|
|
304
413
|
const picked = await promptSelect(rl, {
|
|
305
|
-
title: '
|
|
414
|
+
title: bold('Server flavor'),
|
|
306
415
|
options: [
|
|
307
416
|
{ label: 'happy-server-light (recommended; simplest local install)', value: 'happy-server-light' },
|
|
308
417
|
{ label: 'happy-server (full server; managed infra via Docker)', value: 'happy-server' },
|
|
@@ -313,6 +422,34 @@ async function cmdSetup({ rootDir, argv }) {
|
|
|
313
422
|
});
|
|
314
423
|
}
|
|
315
424
|
|
|
425
|
+
// Dev profile: pick where to store components + worktrees.
|
|
426
|
+
const workspaceDirFlagRaw = (kv.get('--workspace-dir') ?? '').toString().trim();
|
|
427
|
+
const homeDirForWorkspace = getHappyStacksHomeDir(process.env);
|
|
428
|
+
let workspaceDirWanted = workspaceDirFlagRaw ? normalizeWorkspaceDirInput(workspaceDirFlagRaw, { homeDir: homeDirForWorkspace }) : '';
|
|
429
|
+
if (profile === 'dev' && interactive && !workspaceDirWanted) {
|
|
430
|
+
const defaultWorkspaceDir = resolveWorkspaceDirDefault();
|
|
431
|
+
const suggested = defaultWorkspaceDir;
|
|
432
|
+
const helpLines = [
|
|
433
|
+
bold('Workspace location'),
|
|
434
|
+
dim('This is where Happy Stacks will keep:'),
|
|
435
|
+
`- ${dim('components')}: ${cyan(join(suggested, 'components'))}`,
|
|
436
|
+
`- ${dim('worktrees')}: ${cyan(join(suggested, 'components', '.worktrees'))}`,
|
|
437
|
+
'',
|
|
438
|
+
dim('Pick a stable folder that is easy to open in your editor (example: ~/Development/happy).'),
|
|
439
|
+
'',
|
|
440
|
+
].join('\n');
|
|
441
|
+
// eslint-disable-next-line no-console
|
|
442
|
+
console.log(helpLines);
|
|
443
|
+
const raw = await withRl(async (rl) => {
|
|
444
|
+
return await prompt(rl, `Workspace dir (default: ${suggested}): `, { defaultValue: suggested });
|
|
445
|
+
});
|
|
446
|
+
workspaceDirWanted = normalizeWorkspaceDirInput(raw, { homeDir: homeDirForWorkspace });
|
|
447
|
+
}
|
|
448
|
+
if (profile === 'dev' && workspaceDirWanted) {
|
|
449
|
+
// eslint-disable-next-line no-console
|
|
450
|
+
console.log(`${dim('Workspace:')} ${cyan(workspaceDirWanted)}`);
|
|
451
|
+
}
|
|
452
|
+
|
|
316
453
|
const defaultTailscale = false;
|
|
317
454
|
const defaultAutostart = false;
|
|
318
455
|
const defaultMenubar = false;
|
|
@@ -337,7 +474,7 @@ async function cmdSetup({ rootDir, argv }) {
|
|
|
337
474
|
if (profile === 'selfhost') {
|
|
338
475
|
tailscaleWanted = await withRl(async (rl) => {
|
|
339
476
|
const v = await promptSelect(rl, {
|
|
340
|
-
title: '
|
|
477
|
+
title: bold('Remote access'),
|
|
341
478
|
options: [
|
|
342
479
|
{ label: 'no (default)', value: false },
|
|
343
480
|
{ label: 'yes', value: true },
|
|
@@ -350,7 +487,7 @@ async function cmdSetup({ rootDir, argv }) {
|
|
|
350
487
|
if (supportsAutostart) {
|
|
351
488
|
autostartWanted = await withRl(async (rl) => {
|
|
352
489
|
const v = await promptSelect(rl, {
|
|
353
|
-
title: '
|
|
490
|
+
title: bold('Autostart'),
|
|
354
491
|
options: [
|
|
355
492
|
{ label: 'no (default)', value: false },
|
|
356
493
|
{ label: 'yes', value: true },
|
|
@@ -366,7 +503,7 @@ async function cmdSetup({ rootDir, argv }) {
|
|
|
366
503
|
if (supportsMenubar) {
|
|
367
504
|
menubarWanted = await withRl(async (rl) => {
|
|
368
505
|
const v = await promptSelect(rl, {
|
|
369
|
-
title: '
|
|
506
|
+
title: bold('Menu bar (macOS)'),
|
|
370
507
|
options: [
|
|
371
508
|
{ label: 'no (default)', value: false },
|
|
372
509
|
{ label: 'yes', value: true },
|
|
@@ -381,7 +518,7 @@ async function cmdSetup({ rootDir, argv }) {
|
|
|
381
518
|
|
|
382
519
|
startNow = await withRl(async (rl) => {
|
|
383
520
|
const v = await promptSelect(rl, {
|
|
384
|
-
title: 'Start
|
|
521
|
+
title: bold('Start now'),
|
|
385
522
|
options: [
|
|
386
523
|
{ label: 'yes (default)', value: true },
|
|
387
524
|
{ label: 'no', value: false },
|
|
@@ -393,7 +530,7 @@ async function cmdSetup({ rootDir, argv }) {
|
|
|
393
530
|
|
|
394
531
|
authWanted = await withRl(async (rl) => {
|
|
395
532
|
const v = await promptSelect(rl, {
|
|
396
|
-
title: '
|
|
533
|
+
title: bold('Authentication'),
|
|
397
534
|
options: [
|
|
398
535
|
{ label: 'yes (default) — enables Happy UI + mobile access', value: true },
|
|
399
536
|
{ label: 'no — I will authenticate later', value: false },
|
|
@@ -412,7 +549,7 @@ async function cmdSetup({ rootDir, argv }) {
|
|
|
412
549
|
// If you choose to auth now, we’ll also start Happy in the background so login can complete.
|
|
413
550
|
const authNow = await withRl(async (rl) => {
|
|
414
551
|
const v = await promptSelect(rl, {
|
|
415
|
-
title: '
|
|
552
|
+
title: bold('Authentication (optional)'),
|
|
416
553
|
options: [
|
|
417
554
|
{ label: 'no (default) — I will do this later', value: false },
|
|
418
555
|
{ label: 'yes — start Happy in background and login', value: true },
|
|
@@ -429,10 +566,10 @@ async function cmdSetup({ rootDir, argv }) {
|
|
|
429
566
|
|
|
430
567
|
installPath = await withRl(async (rl) => {
|
|
431
568
|
const v = await promptSelect(rl, {
|
|
432
|
-
title:
|
|
569
|
+
title: bold('Shell PATH'),
|
|
433
570
|
options: [
|
|
434
|
-
{ label:
|
|
435
|
-
{ label:
|
|
571
|
+
{ label: `no (default) — you can run via npx / full path`, value: false },
|
|
572
|
+
{ label: `yes — add ${join(getCanonicalHomeDir(), 'bin')} to your PATH`, value: true },
|
|
436
573
|
],
|
|
437
574
|
defaultIndex: installPath ? 1 : 0,
|
|
438
575
|
});
|
|
@@ -465,11 +602,12 @@ async function cmdSetup({ rootDir, argv }) {
|
|
|
465
602
|
}
|
|
466
603
|
|
|
467
604
|
// 1) Ensure plumbing exists (runtime + shims + pointer env). Avoid auto-bootstrap here; setup drives bootstrap explicitly.
|
|
468
|
-
await
|
|
469
|
-
|
|
605
|
+
await runNodeScriptMaybeQuiet({
|
|
606
|
+
label: 'init happy-stacks home',
|
|
470
607
|
rel: 'scripts/init.mjs',
|
|
471
608
|
args: [
|
|
472
609
|
'--no-bootstrap',
|
|
610
|
+
...(profile === 'dev' && workspaceDirWanted ? [`--workspace-dir=${workspaceDirWanted}`] : []),
|
|
473
611
|
...(installPath ? ['--install-path'] : []),
|
|
474
612
|
],
|
|
475
613
|
env: { ...process.env, HAPPY_STACKS_SETUP_CHILD: '1' },
|
|
@@ -487,13 +625,13 @@ async function cmdSetup({ rootDir, argv }) {
|
|
|
487
625
|
// 3) Bootstrap components. Selfhost defaults to upstream; dev defaults to existing bootstrap wizard (forks by default).
|
|
488
626
|
if (profile === 'dev') {
|
|
489
627
|
// Developer setup: keep the existing bootstrap wizard.
|
|
490
|
-
await
|
|
628
|
+
await runNodeScriptMaybeQuiet({ label: 'bootstrap components', rootDir, rel: 'scripts/install.mjs', args: ['--interactive'] });
|
|
491
629
|
|
|
492
630
|
// Optional: offer to create a dedicated dev stack (keeps main stable).
|
|
493
631
|
if (interactive) {
|
|
494
632
|
const createStack = await withRl(async (rl) => {
|
|
495
633
|
return await promptSelect(rl, {
|
|
496
|
-
title: '
|
|
634
|
+
title: bold('Stacks'),
|
|
497
635
|
options: [
|
|
498
636
|
{ label: 'no (default)', value: false },
|
|
499
637
|
{ label: 'yes', value: true },
|
|
@@ -502,7 +640,7 @@ async function cmdSetup({ rootDir, argv }) {
|
|
|
502
640
|
});
|
|
503
641
|
});
|
|
504
642
|
if (createStack) {
|
|
505
|
-
await
|
|
643
|
+
await runNodeScriptMaybeQuiet({ label: 'create dev stack', rootDir, rel: 'scripts/stack.mjs', args: ['new', '--interactive'] });
|
|
506
644
|
}
|
|
507
645
|
|
|
508
646
|
// Guided maintainer-friendly auth defaults (dev key → main → legacy).
|
|
@@ -510,10 +648,12 @@ async function cmdSetup({ rootDir, argv }) {
|
|
|
510
648
|
}
|
|
511
649
|
} else {
|
|
512
650
|
// Selfhost setup: run non-interactively and keep it simple.
|
|
513
|
-
|
|
651
|
+
const repoFlag = serverComponent === 'happy-server-light' ? '--forks' : '--upstream';
|
|
652
|
+
await runNodeScriptMaybeQuiet({
|
|
653
|
+
label: 'bootstrap components',
|
|
514
654
|
rootDir,
|
|
515
655
|
rel: 'scripts/install.mjs',
|
|
516
|
-
args: [`--server=${serverComponent}`,
|
|
656
|
+
args: [`--server=${serverComponent}`, repoFlag],
|
|
517
657
|
});
|
|
518
658
|
}
|
|
519
659
|
|
|
@@ -592,46 +732,20 @@ async function cmdSetup({ rootDir, argv }) {
|
|
|
592
732
|
// eslint-disable-next-line no-console
|
|
593
733
|
console.log('[setup] auth: already configured (access.key exists)');
|
|
594
734
|
} else {
|
|
595
|
-
// Before starting an interactive login, offer the best available shortcut in this order:
|
|
596
|
-
// For selfhost profile:
|
|
597
|
-
// - prefer reusing legacy ~/.happy creds if present (maintainers often already have a local install)
|
|
598
|
-
// - otherwise, run the normal login flow
|
|
599
|
-
let reused = false;
|
|
600
735
|
if (interactive) {
|
|
601
|
-
const
|
|
602
|
-
|
|
603
|
-
const hasLegacy = allowLegacy && existsSync(legacyAccessKey);
|
|
604
|
-
|
|
605
|
-
if (hasLegacy) {
|
|
606
|
-
const options = [];
|
|
607
|
-
if (hasLegacy) {
|
|
608
|
-
options.push({ label: 'reuse legacy ~/.happy (symlink; stays up to date)', value: 'legacy-link' });
|
|
609
|
-
options.push({ label: 'reuse legacy ~/.happy (copy; more isolated)', value: 'legacy-copy' });
|
|
610
|
-
}
|
|
611
|
-
options.push({ label: 'do login flow instead', value: 'login' });
|
|
612
|
-
|
|
613
|
-
const choice = await withRl(async (rl) => {
|
|
614
|
-
return await promptSelect(rl, {
|
|
615
|
-
title:
|
|
616
|
-
'We found existing credentials on this machine. How should Happy Stacks main authenticate?',
|
|
617
|
-
options,
|
|
618
|
-
defaultIndex: 0,
|
|
619
|
-
});
|
|
620
|
-
});
|
|
621
|
-
|
|
622
|
-
if (choice === 'legacy-link') {
|
|
623
|
-
await runNodeScript({ rootDir, rel: 'scripts/auth.mjs', args: ['copy-from', 'legacy', '--allow-main', '--link'] });
|
|
624
|
-
reused = existsSync(accessKey);
|
|
625
|
-
} else if (choice === 'legacy-copy') {
|
|
626
|
-
await runNodeScript({ rootDir, rel: 'scripts/auth.mjs', args: ['copy-from', 'legacy', '--allow-main'] });
|
|
627
|
-
reused = existsSync(accessKey);
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
if (!reused) {
|
|
633
|
-
await runNodeScript({ rootDir, rel: 'scripts/auth.mjs', args: ['login', `--context=${ctx}`] });
|
|
736
|
+
const webappUrl = await resolveMainWebappUrlForAuth({ rootDir, port });
|
|
737
|
+
await guidedStackWebSignupThenLogin({ webappUrl, stackName: 'main' });
|
|
634
738
|
}
|
|
739
|
+
await runNodeScript({
|
|
740
|
+
rootDir,
|
|
741
|
+
rel: 'scripts/auth.mjs',
|
|
742
|
+
args: ['login', `--context=${ctx}`, '--quiet'],
|
|
743
|
+
env: {
|
|
744
|
+
...process.env,
|
|
745
|
+
HAPPY_STACKS_SERVER_PORT: String(port),
|
|
746
|
+
HAPPY_LOCAL_SERVER_PORT: String(port),
|
|
747
|
+
},
|
|
748
|
+
});
|
|
635
749
|
|
|
636
750
|
if (!existsSync(accessKey)) {
|
|
637
751
|
// eslint-disable-next-line no-console
|
|
@@ -662,7 +776,11 @@ async function cmdSetup({ rootDir, argv }) {
|
|
|
662
776
|
// Final tips (keep short).
|
|
663
777
|
if (profile === 'selfhost') {
|
|
664
778
|
// eslint-disable-next-line no-console
|
|
665
|
-
console.log('
|
|
779
|
+
console.log('');
|
|
780
|
+
// eslint-disable-next-line no-console
|
|
781
|
+
console.log(green('✓ Setup complete'));
|
|
782
|
+
// eslint-disable-next-line no-console
|
|
783
|
+
console.log(dim('Useful commands:'));
|
|
666
784
|
// eslint-disable-next-line no-console
|
|
667
785
|
console.log(' happys start');
|
|
668
786
|
// eslint-disable-next-line no-console
|
|
@@ -671,7 +789,11 @@ async function cmdSetup({ rootDir, argv }) {
|
|
|
671
789
|
console.log(' happys service install # macOS/Linux autostart');
|
|
672
790
|
} else {
|
|
673
791
|
// eslint-disable-next-line no-console
|
|
674
|
-
console.log('
|
|
792
|
+
console.log('');
|
|
793
|
+
// eslint-disable-next-line no-console
|
|
794
|
+
console.log(green('✓ Setup complete'));
|
|
795
|
+
// eslint-disable-next-line no-console
|
|
796
|
+
console.log(dim('Useful commands:'));
|
|
675
797
|
// eslint-disable-next-line no-console
|
|
676
798
|
console.log(' happys dev');
|
|
677
799
|
// eslint-disable-next-line no-console
|
|
@@ -691,4 +813,3 @@ main().catch((err) => {
|
|
|
691
813
|
console.error('[setup] failed:', err);
|
|
692
814
|
process.exit(1);
|
|
693
815
|
});
|
|
694
|
-
|