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/scripts/run.mjs
CHANGED
|
@@ -1,25 +1,29 @@
|
|
|
1
|
-
import './utils/env.mjs';
|
|
1
|
+
import './utils/env/env.mjs';
|
|
2
2
|
import { parseArgs } from './utils/cli/args.mjs';
|
|
3
|
-
import { pathExists } from './utils/fs.mjs';
|
|
4
|
-
import { killProcessTree, runCapture, spawnProc } from './utils/proc.mjs';
|
|
5
|
-
import { getComponentDir, getDefaultAutostartPaths, getRootDir } from './utils/paths.mjs';
|
|
6
|
-
import { killPortListeners } from './utils/ports.mjs';
|
|
7
|
-
import { getServerComponentName, isHappyServerRunning, waitForServerReady } from './utils/server.mjs';
|
|
8
|
-
import { ensureCliBuilt, ensureDepsInstalled, pmExecBin, pmSpawnScript, requireDir } from './utils/pm.mjs';
|
|
3
|
+
import { pathExists } from './utils/fs/fs.mjs';
|
|
4
|
+
import { killProcessTree, runCapture, spawnProc } from './utils/proc/proc.mjs';
|
|
5
|
+
import { componentDirEnvKey, getComponentDir, getDefaultAutostartPaths, getRootDir } from './utils/paths/paths.mjs';
|
|
6
|
+
import { killPortListeners } from './utils/net/ports.mjs';
|
|
7
|
+
import { getServerComponentName, isHappyServerRunning, waitForServerReady } from './utils/server/server.mjs';
|
|
8
|
+
import { ensureCliBuilt, ensureDepsInstalled, pmExecBin, pmSpawnScript, requireDir } from './utils/proc/pm.mjs';
|
|
9
9
|
import { homedir } from 'node:os';
|
|
10
10
|
import { join } from 'node:path';
|
|
11
11
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
12
12
|
import { maybeResetTailscaleServe } from './tailscale.mjs';
|
|
13
13
|
import { isDaemonRunning, startLocalDaemonWithAuth, stopLocalDaemon } from './daemon.mjs';
|
|
14
14
|
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
|
|
15
|
-
import { assertServerComponentDirMatches, assertServerPrismaProviderMatches } from './utils/validate.mjs';
|
|
16
|
-
import { applyHappyServerMigrations, ensureHappyServerManagedInfra } from './utils/happy_server_infra.mjs';
|
|
17
|
-
import { getAccountCountForServerComponent, prepareDaemonAuthSeedIfNeeded } from './utils/
|
|
18
|
-
import { recordStackRuntimeStart, recordStackRuntimeUpdate } from './utils/
|
|
19
|
-
import { resolveStackContext } from './utils/
|
|
20
|
-
import { getPublicServerUrlEnvOverride, resolveServerPortFromEnv, resolveServerUrls } from './utils/
|
|
21
|
-
import {
|
|
22
|
-
import { openUrlInBrowser } from './utils/browser.mjs';
|
|
15
|
+
import { assertServerComponentDirMatches, assertServerPrismaProviderMatches } from './utils/server/validate.mjs';
|
|
16
|
+
import { applyHappyServerMigrations, ensureHappyServerManagedInfra } from './utils/server/infra/happy_server_infra.mjs';
|
|
17
|
+
import { getAccountCountForServerComponent, prepareDaemonAuthSeedIfNeeded, resolveAutoCopyFromMainEnabled } from './utils/stack/startup.mjs';
|
|
18
|
+
import { recordStackRuntimeStart, recordStackRuntimeUpdate } from './utils/stack/runtime_state.mjs';
|
|
19
|
+
import { resolveStackContext } from './utils/stack/context.mjs';
|
|
20
|
+
import { getPublicServerUrlEnvOverride, resolveServerPortFromEnv, resolveServerUrls } from './utils/server/urls.mjs';
|
|
21
|
+
import { preferStackLocalhostUrl } from './utils/paths/localhost_host.mjs';
|
|
22
|
+
import { openUrlInBrowser } from './utils/ui/browser.mjs';
|
|
23
|
+
import { ensureDevExpoServer } from './utils/dev/expo_dev.mjs';
|
|
24
|
+
import { maybeRunInteractiveStackAuthSetup } from './utils/auth/interactive_stack_auth.mjs';
|
|
25
|
+
import { getInvokedCwd, inferComponentFromCwd } from './utils/cli/cwd_scope.mjs';
|
|
26
|
+
import { daemonStartGate, formatDaemonAuthRequiredError } from './utils/auth/daemon_gate.mjs';
|
|
23
27
|
|
|
24
28
|
/**
|
|
25
29
|
* Run the local stack in "production-like" mode:
|
|
@@ -27,7 +31,7 @@ import { openUrlInBrowser } from './utils/browser.mjs';
|
|
|
27
31
|
* - happy-cli daemon
|
|
28
32
|
* - serve prebuilt UI via happy-server-light (/)
|
|
29
33
|
*
|
|
30
|
-
*
|
|
34
|
+
* Optional: Expo dev-client Metro for mobile reviewers (`--mobile`).
|
|
31
35
|
*/
|
|
32
36
|
|
|
33
37
|
async function main() {
|
|
@@ -37,12 +41,15 @@ async function main() {
|
|
|
37
41
|
if (wantsHelp(argv, { flags })) {
|
|
38
42
|
printResult({
|
|
39
43
|
json,
|
|
40
|
-
data: { flags: ['--server=happy-server|happy-server-light', '--no-ui', '--no-daemon', '--restart', '--no-browser'], json: true },
|
|
44
|
+
data: { flags: ['--server=happy-server|happy-server-light', '--no-ui', '--no-daemon', '--restart', '--no-browser', '--mobile'], json: true },
|
|
41
45
|
text: [
|
|
42
46
|
'[start] usage:',
|
|
43
47
|
' happys start [--server=happy-server|happy-server-light] [--restart] [--json]',
|
|
44
48
|
' (legacy in a cloned repo): pnpm start [-- --server=happy-server|happy-server-light] [--json]',
|
|
45
49
|
' note: --json prints the resolved config (dry-run) and exits.',
|
|
50
|
+
'',
|
|
51
|
+
'note:',
|
|
52
|
+
' If run from inside a component checkout/worktree, that checkout is used for this run (without requiring `happys wt use`).',
|
|
46
53
|
].join('\n'),
|
|
47
54
|
});
|
|
48
55
|
return;
|
|
@@ -50,6 +57,20 @@ async function main() {
|
|
|
50
57
|
|
|
51
58
|
const rootDir = getRootDir(import.meta.url);
|
|
52
59
|
|
|
60
|
+
const inferred = inferComponentFromCwd({
|
|
61
|
+
rootDir,
|
|
62
|
+
invokedCwd: getInvokedCwd(process.env),
|
|
63
|
+
components: ['happy', 'happy-cli', 'happy-server-light', 'happy-server'],
|
|
64
|
+
});
|
|
65
|
+
if (inferred) {
|
|
66
|
+
const stacksKey = componentDirEnvKey(inferred.component);
|
|
67
|
+
const legacyKey = stacksKey.replace(/^HAPPY_STACKS_/, 'HAPPY_LOCAL_');
|
|
68
|
+
// Stack env should win. Only infer from CWD when the component dir isn't already configured.
|
|
69
|
+
if (!(process.env[stacksKey] ?? '').toString().trim() && !(process.env[legacyKey] ?? '').toString().trim()) {
|
|
70
|
+
process.env[stacksKey] = inferred.repoDir;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
53
74
|
const serverPort = resolveServerPortFromEnv({ defaultPort: 3005 });
|
|
54
75
|
|
|
55
76
|
// Internal URL used by local processes on this machine.
|
|
@@ -67,6 +88,7 @@ async function main() {
|
|
|
67
88
|
const startDaemon = !flags.has('--no-daemon') && (process.env.HAPPY_LOCAL_DAEMON ?? '1') !== '0';
|
|
68
89
|
const serveUiWanted = !flags.has('--no-ui') && (process.env.HAPPY_LOCAL_SERVE_UI ?? '1') !== '0';
|
|
69
90
|
const serveUi = serveUiWanted;
|
|
91
|
+
const startMobile = flags.has('--mobile') || flags.has('--with-mobile');
|
|
70
92
|
const noBrowser = flags.has('--no-browser') || (process.env.HAPPY_STACKS_NO_BROWSER ?? process.env.HAPPY_LOCAL_NO_BROWSER ?? '').toString().trim() === '1';
|
|
71
93
|
const uiPrefix = process.env.HAPPY_LOCAL_UI_PREFIX?.trim() ? process.env.HAPPY_LOCAL_UI_PREFIX.trim() : '/';
|
|
72
94
|
const autostart = getDefaultAutostartPaths();
|
|
@@ -78,12 +100,16 @@ async function main() {
|
|
|
78
100
|
|
|
79
101
|
const serverDir = getComponentDir(rootDir, serverComponentName);
|
|
80
102
|
const cliDir = getComponentDir(rootDir, 'happy-cli');
|
|
103
|
+
const uiDir = getComponentDir(rootDir, 'happy');
|
|
81
104
|
|
|
82
105
|
assertServerComponentDirMatches({ rootDir, serverComponentName, serverDir });
|
|
83
106
|
assertServerPrismaProviderMatches({ serverComponentName, serverDir });
|
|
84
107
|
|
|
85
108
|
await requireDir(serverComponentName, serverDir);
|
|
86
109
|
await requireDir('happy-cli', cliDir);
|
|
110
|
+
if (startMobile) {
|
|
111
|
+
await requireDir('happy', uiDir);
|
|
112
|
+
}
|
|
87
113
|
|
|
88
114
|
const cliBin = join(cliDir, 'bin', 'happy.mjs');
|
|
89
115
|
|
|
@@ -99,12 +125,14 @@ async function main() {
|
|
|
99
125
|
mode: 'start',
|
|
100
126
|
serverComponentName,
|
|
101
127
|
serverDir,
|
|
128
|
+
uiDir,
|
|
102
129
|
cliDir,
|
|
103
130
|
serverPort,
|
|
104
131
|
internalServerUrl,
|
|
105
132
|
publicServerUrl,
|
|
106
133
|
startDaemon,
|
|
107
134
|
serveUi,
|
|
135
|
+
startMobile,
|
|
108
136
|
uiPrefix,
|
|
109
137
|
uiBuildDir,
|
|
110
138
|
cliHomeDir,
|
|
@@ -125,7 +153,7 @@ async function main() {
|
|
|
125
153
|
let shuttingDown = false;
|
|
126
154
|
const baseEnv = { ...process.env };
|
|
127
155
|
const stackCtx = resolveStackContext({ env: baseEnv, autostart });
|
|
128
|
-
const { stackMode, runtimeStatePath, stackName, ephemeral } = stackCtx;
|
|
156
|
+
const { stackMode, runtimeStatePath, stackName, envPath, ephemeral } = stackCtx;
|
|
129
157
|
|
|
130
158
|
// Ensure happy-cli is install+build ready before starting the daemon.
|
|
131
159
|
const buildCli = (baseEnv.HAPPY_STACKS_CLI_BUILD ?? baseEnv.HAPPY_LOCAL_CLI_BUILD ?? '1').toString().trim() !== '0';
|
|
@@ -133,12 +161,18 @@ async function main() {
|
|
|
133
161
|
|
|
134
162
|
// Ensure server deps exist before any Prisma/docker work.
|
|
135
163
|
await ensureDepsInstalled(serverDir, serverComponentName);
|
|
164
|
+
if (startMobile) {
|
|
165
|
+
await ensureDepsInstalled(uiDir, 'happy');
|
|
166
|
+
}
|
|
136
167
|
|
|
137
168
|
// Public URL automation:
|
|
138
169
|
// - Only the main stack should ever auto-enable Tailscale Serve by default.
|
|
139
170
|
// - Non-main stacks default to localhost unless the user explicitly configured a public URL
|
|
140
171
|
// OR Tailscale Serve is already configured for this stack's internal URL (status matches).
|
|
141
|
-
const allowEnableTailscale =
|
|
172
|
+
const allowEnableTailscale =
|
|
173
|
+
!stackMode ||
|
|
174
|
+
stackName === 'main' ||
|
|
175
|
+
(baseEnv.HAPPY_STACKS_TAILSCALE_SERVE ?? baseEnv.HAPPY_LOCAL_TAILSCALE_SERVE ?? '0').toString().trim() === '1';
|
|
142
176
|
const resolvedUrls = await resolveServerUrls({ env: baseEnv, serverPort, allowEnable: allowEnableTailscale });
|
|
143
177
|
if (stackMode && stackName !== 'main' && !resolvedUrls.envPublicUrl) {
|
|
144
178
|
const src = String(resolvedUrls.publicServerUrlSource ?? '');
|
|
@@ -331,9 +365,8 @@ async function main() {
|
|
|
331
365
|
// Auto-open UI (interactive only) using the stack-scoped hostname when applicable.
|
|
332
366
|
const isInteractive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
333
367
|
if (isInteractive && !noBrowser) {
|
|
334
|
-
const host = resolveLocalhostHost({ stackMode, stackName: autostart.stackName });
|
|
335
368
|
const prefix = uiPrefix.startsWith('/') ? uiPrefix : `/${uiPrefix}`;
|
|
336
|
-
const openUrl = `http
|
|
369
|
+
const openUrl = await preferStackLocalhostUrl(`http://localhost:${serverPort}${prefix}`, { stackName: autostart.stackName });
|
|
337
370
|
const res = await openUrlInBrowser(openUrl);
|
|
338
371
|
if (!res.ok) {
|
|
339
372
|
console.warn(`[local] ui: failed to open browser automatically (${res.error}).`);
|
|
@@ -343,6 +376,16 @@ async function main() {
|
|
|
343
376
|
|
|
344
377
|
// Daemon
|
|
345
378
|
if (startDaemon) {
|
|
379
|
+
const gate = daemonStartGate({ env: baseEnv, cliHomeDir });
|
|
380
|
+
if (!gate.ok) {
|
|
381
|
+
const isInteractive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
382
|
+
// In orchestrated auth flows, keep server/UI up and let the orchestrator start daemon post-auth.
|
|
383
|
+
if (gate.reason === 'auth_flow_missing_credentials') {
|
|
384
|
+
console.log('[local] auth flow: skipping daemon start until credentials exist');
|
|
385
|
+
} else if (!isInteractive) {
|
|
386
|
+
throw new Error(formatDaemonAuthRequiredError({ stackName: autostart.stackName, cliHomeDir }));
|
|
387
|
+
}
|
|
388
|
+
} else {
|
|
346
389
|
const isInteractive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
347
390
|
if (serverComponentName === 'happy-server' && happyServerAccountCount == null) {
|
|
348
391
|
const acct = await getAccountCountForServerComponent({
|
|
@@ -355,6 +398,16 @@ async function main() {
|
|
|
355
398
|
}
|
|
356
399
|
const accountCount =
|
|
357
400
|
serverComponentName === 'happy-server-light' ? serverLightAccountCount : happyServerAccountCount;
|
|
401
|
+
const autoSeedEnabled = resolveAutoCopyFromMainEnabled({ env: baseEnv, stackName: autostart.stackName, isInteractive });
|
|
402
|
+
await maybeRunInteractiveStackAuthSetup({
|
|
403
|
+
rootDir,
|
|
404
|
+
env: baseEnv,
|
|
405
|
+
stackName: autostart.stackName,
|
|
406
|
+
cliHomeDir,
|
|
407
|
+
accountCount,
|
|
408
|
+
isInteractive,
|
|
409
|
+
autoSeedEnabled,
|
|
410
|
+
});
|
|
358
411
|
await prepareDaemonAuthSeedIfNeeded({
|
|
359
412
|
rootDir,
|
|
360
413
|
env: baseEnv,
|
|
@@ -372,6 +425,27 @@ async function main() {
|
|
|
372
425
|
publicServerUrl,
|
|
373
426
|
isShuttingDown: () => shuttingDown,
|
|
374
427
|
forceRestart: restart,
|
|
428
|
+
env: baseEnv,
|
|
429
|
+
stackName: autostart.stackName,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Optional: start Expo dev-client Metro for mobile reviewers.
|
|
435
|
+
if (startMobile) {
|
|
436
|
+
await ensureDevExpoServer({
|
|
437
|
+
startUi: false,
|
|
438
|
+
startMobile: true,
|
|
439
|
+
uiDir,
|
|
440
|
+
autostart,
|
|
441
|
+
baseEnv,
|
|
442
|
+
apiServerUrl: publicServerUrl,
|
|
443
|
+
restart,
|
|
444
|
+
stackMode,
|
|
445
|
+
runtimeStatePath,
|
|
446
|
+
stackName,
|
|
447
|
+
envPath,
|
|
448
|
+
children,
|
|
375
449
|
});
|
|
376
450
|
}
|
|
377
451
|
|
package/scripts/self.mjs
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
import './utils/env.mjs';
|
|
1
|
+
import './utils/env/env.mjs';
|
|
2
2
|
|
|
3
3
|
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
4
4
|
import { existsSync } from 'node:fs';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
|
|
7
7
|
import { parseArgs } from './utils/cli/args.mjs';
|
|
8
|
-
import { pathExists } from './utils/fs.mjs';
|
|
9
|
-
import { run, runCapture } from './utils/proc.mjs';
|
|
10
|
-
import { expandHome } from './utils/canonical_home.mjs';
|
|
11
|
-
import { getHappyStacksHomeDir, getRootDir } from './utils/paths.mjs';
|
|
8
|
+
import { pathExists } from './utils/fs/fs.mjs';
|
|
9
|
+
import { run, runCapture } from './utils/proc/proc.mjs';
|
|
10
|
+
import { expandHome } from './utils/paths/canonical_home.mjs';
|
|
11
|
+
import { getHappyStacksHomeDir, getRootDir } from './utils/paths/paths.mjs';
|
|
12
12
|
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
|
|
13
|
-
import { getRuntimeDir } from './utils/runtime.mjs';
|
|
13
|
+
import { getRuntimeDir } from './utils/paths/runtime.mjs';
|
|
14
|
+
import { readJsonIfExists } from './utils/fs/json.mjs';
|
|
15
|
+
import { readPackageJsonVersion } from './utils/fs/package_json.mjs';
|
|
14
16
|
|
|
15
17
|
function cachePaths() {
|
|
16
18
|
const home = getHappyStacksHomeDir();
|
|
@@ -21,15 +23,6 @@ function cachePaths() {
|
|
|
21
23
|
};
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
async function readJsonSafe(path) {
|
|
25
|
-
try {
|
|
26
|
-
const raw = await readFile(path, 'utf-8');
|
|
27
|
-
return JSON.parse(raw);
|
|
28
|
-
} catch {
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
26
|
async function writeJsonSafe(path, obj) {
|
|
34
27
|
try {
|
|
35
28
|
await mkdir(join(path, '..'), { recursive: true });
|
|
@@ -43,25 +36,14 @@ async function writeJsonSafe(path, obj) {
|
|
|
43
36
|
}
|
|
44
37
|
}
|
|
45
38
|
|
|
46
|
-
async function readPkgVersion(pkgJsonPath) {
|
|
47
|
-
try {
|
|
48
|
-
const raw = await readFile(pkgJsonPath, 'utf-8');
|
|
49
|
-
const pkg = JSON.parse(raw);
|
|
50
|
-
const v = String(pkg.version ?? '').trim();
|
|
51
|
-
return v || null;
|
|
52
|
-
} catch {
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
39
|
async function getRuntimeInstalledVersion() {
|
|
58
40
|
const runtimeDir = getRuntimeDir();
|
|
59
41
|
const pkgJson = join(runtimeDir, 'node_modules', 'happy-stacks', 'package.json');
|
|
60
|
-
return await
|
|
42
|
+
return await readPackageJsonVersion(pkgJson);
|
|
61
43
|
}
|
|
62
44
|
|
|
63
45
|
async function getInvokerVersion({ rootDir }) {
|
|
64
|
-
return await
|
|
46
|
+
return await readPackageJsonVersion(join(rootDir, 'package.json'));
|
|
65
47
|
}
|
|
66
48
|
|
|
67
49
|
async function fetchLatestVersion() {
|
|
@@ -102,7 +84,7 @@ async function cmdStatus({ rootDir, argv }) {
|
|
|
102
84
|
const runtimeDir = getRuntimeDir();
|
|
103
85
|
const runtimeVersion = await getRuntimeInstalledVersion();
|
|
104
86
|
|
|
105
|
-
const cached = await
|
|
87
|
+
const cached = await readJsonIfExists(updateJson, { defaultValue: null });
|
|
106
88
|
|
|
107
89
|
let latest = cached?.latest ?? null;
|
|
108
90
|
let checkedAt = cached?.checkedAt ?? null;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import './utils/env.mjs';
|
|
1
|
+
import './utils/env/env.mjs';
|
|
2
2
|
import { parseArgs } from './utils/cli/args.mjs';
|
|
3
|
-
import { getRootDir } from './utils/paths.mjs';
|
|
4
|
-
import { ensureEnvFileUpdated } from './utils/env_file.mjs';
|
|
5
|
-
import { resolveUserConfigEnvPath } from './utils/config.mjs';
|
|
3
|
+
import { getRootDir } from './utils/paths/paths.mjs';
|
|
4
|
+
import { ensureEnvFileUpdated } from './utils/env/env_file.mjs';
|
|
5
|
+
import { resolveUserConfigEnvPath } from './utils/env/config.mjs';
|
|
6
6
|
import { isTty, promptSelect, withRl } from './utils/cli/wizard.mjs';
|
|
7
7
|
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
|
|
8
8
|
|
package/scripts/service.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import './utils/env.mjs';
|
|
2
|
-
import { run, runCapture } from './utils/proc.mjs';
|
|
3
|
-
import { getDefaultAutostartPaths, getRootDir, resolveStackEnvPath } from './utils/paths.mjs';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import './utils/env/env.mjs';
|
|
2
|
+
import { run, runCapture } from './utils/proc/proc.mjs';
|
|
3
|
+
import { getComponentDir, getDefaultAutostartPaths, getRootDir, resolveStackEnvPath } from './utils/paths/paths.mjs';
|
|
4
|
+
import { getInternalServerUrl, getPublicServerUrlEnvOverride } from './utils/server/urls.mjs';
|
|
5
|
+
import { ensureMacAutostartDisabled, ensureMacAutostartEnabled } from './utils/service/autostart_darwin.mjs';
|
|
6
|
+
import { getCanonicalHomeDir } from './utils/env/config.mjs';
|
|
7
|
+
import { isSandboxed, sandboxAllowsGlobalSideEffects } from './utils/env/sandbox.mjs';
|
|
7
8
|
import { spawn } from 'node:child_process';
|
|
8
9
|
import { homedir } from 'node:os';
|
|
9
10
|
import { existsSync } from 'node:fs';
|
|
@@ -12,6 +13,7 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
12
13
|
import { fileURLToPath } from 'node:url';
|
|
13
14
|
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
|
|
14
15
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
16
|
+
import { readLastLines } from './utils/fs/tail.mjs';
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
19
|
* Manage the autostart service installed by `happys bootstrap -- --autostart`.
|
|
@@ -35,11 +37,6 @@ function getUid() {
|
|
|
35
37
|
return Number.isFinite(n) ? n : null;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
function getInternalUrl() {
|
|
39
|
-
const port = process.env.HAPPY_LOCAL_SERVER_PORT?.trim() ? Number(process.env.HAPPY_LOCAL_SERVER_PORT) : 3005;
|
|
40
|
-
return `http://127.0.0.1:${port}`;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
40
|
function getAutostartEnv({ rootDir }) {
|
|
44
41
|
// IMPORTANT:
|
|
45
42
|
// LaunchAgents should NOT bake the entire config into the plist, because that would require
|
|
@@ -267,18 +264,21 @@ async function startLaunchAgent({ persistent }) {
|
|
|
267
264
|
|
|
268
265
|
async function postStartDiagnostics() {
|
|
269
266
|
const rootDir = getRootDir(import.meta.url);
|
|
270
|
-
const internalUrl =
|
|
267
|
+
const internalUrl = getInternalServerUrl({ env: process.env, defaultPort: 3005 }).internalServerUrl;
|
|
271
268
|
|
|
272
269
|
const cliHomeDir = process.env.HAPPY_LOCAL_CLI_HOME_DIR?.trim()
|
|
273
270
|
? process.env.HAPPY_LOCAL_CLI_HOME_DIR.trim().replace(/^~(?=\/)/, homedir())
|
|
274
271
|
: join(getDefaultAutostartPaths().baseDir, 'cli');
|
|
275
272
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
273
|
+
let port = 3005;
|
|
274
|
+
try {
|
|
275
|
+
port = Number(new URL(internalUrl).port || 0) || 3005;
|
|
276
|
+
} catch {
|
|
277
|
+
port = 3005;
|
|
278
|
+
}
|
|
279
|
+
const { publicServerUrl: publicUrl } = getPublicServerUrlEnvOverride({ env: process.env, serverPort: port });
|
|
280
280
|
|
|
281
|
-
const cliDir =
|
|
281
|
+
const cliDir = getComponentDir(rootDir, 'happy-cli');
|
|
282
282
|
const cliBin = join(cliDir, 'bin', 'happy.mjs');
|
|
283
283
|
|
|
284
284
|
const accessKey = join(cliHomeDir, 'access.key');
|
|
@@ -286,16 +286,6 @@ async function postStartDiagnostics() {
|
|
|
286
286
|
const lockFile = join(cliHomeDir, 'daemon.state.json.lock');
|
|
287
287
|
const logsDir = join(cliHomeDir, 'logs');
|
|
288
288
|
|
|
289
|
-
const readLastLines = async (path, lines = 60) => {
|
|
290
|
-
try {
|
|
291
|
-
const raw = await readFile(path, 'utf-8');
|
|
292
|
-
const parts = raw.split('\n');
|
|
293
|
-
return parts.slice(Math.max(0, parts.length - lines)).join('\n');
|
|
294
|
-
} catch {
|
|
295
|
-
return null;
|
|
296
|
-
}
|
|
297
|
-
};
|
|
298
|
-
|
|
299
289
|
const latestDaemonLog = async () => {
|
|
300
290
|
try {
|
|
301
291
|
const ls = await runCapture('bash', ['-lc', `ls -1t "${logsDir}"/*-daemon.log 2>/dev/null | head -1 || true`]);
|
|
@@ -461,7 +451,7 @@ async function waitForLaunchAgentStopped({ timeoutMs = 8000 } = {}) {
|
|
|
461
451
|
|
|
462
452
|
async function showStatus() {
|
|
463
453
|
const { plistPath, stdoutPath, stderrPath, label } = getDefaultAutostartPaths();
|
|
464
|
-
const internalUrl =
|
|
454
|
+
const internalUrl = getInternalServerUrl({ env: process.env, defaultPort: 3005 }).internalServerUrl;
|
|
465
455
|
|
|
466
456
|
console.log(`label: ${label}`);
|
|
467
457
|
console.log(`plist: ${plistPath} ${existsSync(plistPath) ? '(present)' : '(missing)'}`);
|
|
@@ -550,7 +540,7 @@ async function main() {
|
|
|
550
540
|
return;
|
|
551
541
|
case 'status':
|
|
552
542
|
if (json) {
|
|
553
|
-
const internalUrl =
|
|
543
|
+
const internalUrl = getInternalServerUrl({ env: process.env, defaultPort: 3005 }).internalServerUrl;
|
|
554
544
|
let health = null;
|
|
555
545
|
try {
|
|
556
546
|
const res = await fetch(`${internalUrl}/health`, { method: 'GET' });
|