happy-stacks 0.2.0 → 0.3.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 +59 -22
- package/bin/happys.mjs +2 -2
- package/package.json +1 -1
- package/scripts/auth.mjs +49 -202
- package/scripts/build.mjs +5 -6
- package/scripts/cli-link.mjs +3 -3
- package/scripts/completion.mjs +5 -5
- package/scripts/daemon.mjs +9 -17
- package/scripts/dev.mjs +18 -27
- package/scripts/doctor.mjs +20 -36
- package/scripts/edison.mjs +102 -77
- package/scripts/happy.mjs +8 -19
- package/scripts/init.mjs +5 -13
- package/scripts/install.mjs +8 -8
- package/scripts/lint.mjs +8 -29
- package/scripts/menubar.mjs +6 -13
- package/scripts/migrate.mjs +11 -21
- package/scripts/mobile.mjs +13 -12
- package/scripts/run.mjs +15 -15
- package/scripts/self.mjs +11 -29
- package/scripts/server_flavor.mjs +4 -4
- package/scripts/service.mjs +18 -28
- package/scripts/setup.mjs +26 -122
- package/scripts/setup_pr.mjs +11 -28
- package/scripts/stack.mjs +111 -161
- package/scripts/stop.mjs +3 -3
- package/scripts/tailscale.mjs +7 -10
- package/scripts/test.mjs +8 -29
- package/scripts/tui.mjs +8 -38
- package/scripts/typecheck.mjs +8 -29
- package/scripts/ui_gateway.mjs +1 -1
- package/scripts/uninstall.mjs +6 -6
- package/scripts/utils/{dev_auth_key.mjs → auth/dev_key.mjs} +2 -8
- package/scripts/utils/{auth_files.mjs → auth/files.mjs} +2 -4
- package/scripts/utils/{handy_master_secret.mjs → auth/handy_master_secret.mjs} +6 -32
- package/scripts/utils/cli/flags.mjs +17 -0
- package/scripts/utils/cli/normalize.mjs +16 -0
- package/scripts/utils/cli/smoke_help.mjs +2 -2
- 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} +4 -4
- package/scripts/utils/{dev_expo_web.mjs → dev/expo_web.mjs} +5 -5
- package/scripts/utils/{dev_server.mjs → dev/server.mjs} +7 -7
- 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.mjs → expo/expo.mjs} +3 -9
- 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/refs.mjs +26 -0
- package/scripts/utils/{worktrees.mjs → git/worktrees.mjs} +3 -3
- package/scripts/utils/net/dns.mjs +10 -0
- package/scripts/utils/{ports.mjs → net/ports.mjs} +3 -5
- package/scripts/utils/{localhost_host.mjs → paths/localhost_host.mjs} +2 -10
- package/scripts/utils/{paths.mjs → paths/paths.mjs} +10 -7
- 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/pids.mjs +11 -0
- package/scripts/utils/{pm.mjs → proc/pm.mjs} +65 -152
- package/scripts/utils/{proc.mjs → proc/proc.mjs} +1 -0
- package/scripts/utils/{happy_server_infra.mjs → server/infra/happy_server_infra.mjs} +10 -49
- package/scripts/utils/server/port.mjs +68 -0
- package/scripts/utils/{server.mjs → server/server.mjs} +12 -0
- package/scripts/utils/server/urls.mjs +91 -0
- package/scripts/utils/{validate.mjs → server/validate.mjs} +1 -1
- package/scripts/utils/service/autostart_darwin.mjs +142 -0
- package/scripts/utils/{stack_context.mjs → stack/context.mjs} +2 -2
- 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_runtime_state.mjs → stack/runtime_state.mjs} +10 -27
- package/scripts/utils/{stacks.mjs → stack/stacks.mjs} +9 -2
- package/scripts/utils/{stack_startup.mjs → stack/startup.mjs} +2 -2
- package/scripts/utils/{stack_stop.mjs → stack/stop.mjs} +9 -15
- package/scripts/utils/{browser.mjs → ui/browser.mjs} +1 -1
- package/scripts/utils/ui/text.mjs +16 -0
- package/scripts/where.mjs +6 -6
- package/scripts/worktrees.mjs +30 -58
- package/scripts/utils/server_port.mjs +0 -9
- package/scripts/utils/server_urls.mjs +0 -54
- /package/scripts/utils/{auth_login_ux.mjs → auth/login_ux.mjs} +0 -0
- /package/scripts/utils/{auth_sources.mjs → auth/sources.mjs} +0 -0
- /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/stack.mjs
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import './utils/env.mjs';
|
|
1
|
+
import './utils/env/env.mjs';
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
3
|
import { chmod, copyFile, mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
5
5
|
import { existsSync } from 'node:fs';
|
|
6
|
-
|
|
6
|
+
// NOTE: random bytes usage centralized in scripts/utils/crypto/tokens.mjs
|
|
7
7
|
import { homedir } from 'node:os';
|
|
8
|
+
import { ensureDir, readTextIfExists, readTextOrEmpty } from './utils/fs/ops.mjs';
|
|
8
9
|
|
|
9
10
|
import { parseArgs } from './utils/cli/args.mjs';
|
|
10
|
-
import { killProcessTree, run, runCapture } from './utils/proc.mjs';
|
|
11
|
-
import { getComponentDir, getComponentsDir, getHappyStacksHomeDir, getLegacyStorageRoot, getRootDir, getStacksStorageRoot, resolveStackEnvPath } from './utils/paths.mjs';
|
|
12
|
-
import { isTcpPortFree, pickNextFreeTcpPort } from './utils/ports.mjs';
|
|
11
|
+
import { killProcessTree, run, runCapture } from './utils/proc/proc.mjs';
|
|
12
|
+
import { getComponentDir, getComponentsDir, getHappyStacksHomeDir, getLegacyStorageRoot, getRootDir, getStacksStorageRoot, resolveStackEnvPath } from './utils/paths/paths.mjs';
|
|
13
|
+
import { isTcpPortFree, pickNextFreeTcpPort } from './utils/net/ports.mjs';
|
|
13
14
|
import {
|
|
14
15
|
createWorktree,
|
|
15
16
|
createWorktreeFromBaseWorktree,
|
|
@@ -17,62 +18,49 @@ import {
|
|
|
17
18
|
isComponentWorktreePath,
|
|
18
19
|
resolveComponentSpecToDir,
|
|
19
20
|
worktreeSpecFromDir,
|
|
20
|
-
} from './utils/worktrees.mjs';
|
|
21
|
+
} from './utils/git/worktrees.mjs';
|
|
21
22
|
import { isTty, prompt, promptWorktreeSource, withRl } from './utils/cli/wizard.mjs';
|
|
22
|
-
import {
|
|
23
|
+
import { parseEnvToObject } from './utils/env/dotenv.mjs';
|
|
23
24
|
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
|
|
24
|
-
import { ensureEnvFilePruned, ensureEnvFileUpdated } from './utils/env_file.mjs';
|
|
25
|
-
import { listAllStackNames } from './utils/stacks.mjs';
|
|
26
|
-
import { stopStackWithEnv } from './utils/
|
|
27
|
-
import { writeDevAuthKey } from './utils/
|
|
28
|
-
import { startDevServer } from './utils/
|
|
29
|
-
import { startDevExpoWebUi } from './utils/
|
|
30
|
-
import { requireDir } from './utils/pm.mjs';
|
|
31
|
-
import { waitForHttpOk } from './utils/server.mjs';
|
|
32
|
-
import { resolveLocalhostHost } from './utils/localhost_host.mjs';
|
|
33
|
-
import { openUrlInBrowser } from './utils/browser.mjs';
|
|
34
|
-
import { copyFileIfMissing, linkFileIfMissing, writeSecretFileIfMissing } from './utils/
|
|
35
|
-
import { getLegacyHappyBaseDir, isLegacyAuthSourceName } from './utils/
|
|
36
|
-
import { resolveAuthSeedFromEnv } from './utils/
|
|
37
|
-
import { getHomeEnvLocalPath } from './utils/config.mjs';
|
|
38
|
-
import { isSandboxed, sandboxAllowsGlobalSideEffects } from './utils/sandbox.mjs';
|
|
39
|
-
import { resolveHandyMasterSecretFromStack } from './utils/handy_master_secret.mjs';
|
|
25
|
+
import { ensureEnvFilePruned, ensureEnvFileUpdated } from './utils/env/env_file.mjs';
|
|
26
|
+
import { listAllStackNames, stackExistsSync } from './utils/stack/stacks.mjs';
|
|
27
|
+
import { stopStackWithEnv } from './utils/stack/stop.mjs';
|
|
28
|
+
import { writeDevAuthKey } from './utils/auth/dev_key.mjs';
|
|
29
|
+
import { startDevServer } from './utils/dev/server.mjs';
|
|
30
|
+
import { startDevExpoWebUi } from './utils/dev/expo_web.mjs';
|
|
31
|
+
import { requireDir } from './utils/proc/pm.mjs';
|
|
32
|
+
import { waitForHttpOk } from './utils/server/server.mjs';
|
|
33
|
+
import { resolveLocalhostHost } from './utils/paths/localhost_host.mjs';
|
|
34
|
+
import { openUrlInBrowser } from './utils/ui/browser.mjs';
|
|
35
|
+
import { copyFileIfMissing, linkFileIfMissing, writeSecretFileIfMissing } from './utils/auth/files.mjs';
|
|
36
|
+
import { getLegacyHappyBaseDir, isLegacyAuthSourceName } from './utils/auth/sources.mjs';
|
|
37
|
+
import { resolveAuthSeedFromEnv } from './utils/stack/startup.mjs';
|
|
38
|
+
import { getHomeEnvLocalPath } from './utils/env/config.mjs';
|
|
39
|
+
import { isSandboxed, sandboxAllowsGlobalSideEffects } from './utils/env/sandbox.mjs';
|
|
40
|
+
import { resolveHandyMasterSecretFromStack } from './utils/auth/handy_master_secret.mjs';
|
|
41
|
+
import { readPinnedServerPortFromEnvFile } from './utils/server/port.mjs';
|
|
42
|
+
import { getEnvValue, getEnvValueAny } from './utils/env/values.mjs';
|
|
43
|
+
import { sanitizeDnsLabel } from './utils/net/dns.mjs';
|
|
44
|
+
import { coercePort, listPortsFromEnvObject, STACK_RESERVED_PORT_KEYS } from './utils/server/port.mjs';
|
|
40
45
|
import {
|
|
41
46
|
deleteStackRuntimeStateFile,
|
|
42
47
|
getStackRuntimeStatePath,
|
|
43
48
|
isPidAlive,
|
|
44
49
|
recordStackRuntimeStart,
|
|
45
50
|
readStackRuntimeStateFile,
|
|
46
|
-
} from './utils/
|
|
47
|
-
import { killPid } from './utils/expo.mjs';
|
|
48
|
-
import {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function getEnvValueAny(obj, keys) {
|
|
56
|
-
for (const k of keys) {
|
|
57
|
-
const v = getEnvValue(obj, k);
|
|
58
|
-
if (v) return v;
|
|
59
|
-
}
|
|
60
|
-
return '';
|
|
61
|
-
}
|
|
51
|
+
} from './utils/stack/runtime_state.mjs';
|
|
52
|
+
import { killPid } from './utils/expo/expo.mjs';
|
|
53
|
+
import { getCliHomeDirFromEnvOrDefault, getServerLightDataDirFromEnvOrDefault } from './utils/stack/dirs.mjs';
|
|
54
|
+
import { randomToken } from './utils/crypto/tokens.mjs';
|
|
55
|
+
import { killPidOwnedByStack } from './utils/proc/ownership.mjs';
|
|
56
|
+
import { sanitizeSlugPart } from './utils/git/refs.mjs';
|
|
57
|
+
import { isCursorInstalled, openWorkspaceInEditor, writeStackCodeWorkspace } from './utils/stack/editor_workspace.mjs';
|
|
62
58
|
|
|
63
59
|
function stackNameFromArg(positionals, idx) {
|
|
64
60
|
const name = positionals[idx]?.trim() ? positionals[idx].trim() : '';
|
|
65
61
|
return name;
|
|
66
62
|
}
|
|
67
63
|
|
|
68
|
-
function getStackDir(stackName) {
|
|
69
|
-
return resolveStackEnvPath(stackName).baseDir;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function getStackEnvPath(stackName) {
|
|
73
|
-
return resolveStackEnvPath(stackName).envPath;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
64
|
function getDefaultPortStart() {
|
|
77
65
|
const raw = process.env.HAPPY_STACKS_STACK_PORT_START?.trim()
|
|
78
66
|
? process.env.HAPPY_STACKS_STACK_PORT_START.trim()
|
|
@@ -97,34 +85,14 @@ async function pickNextFreePort(startPort, { reservedPorts = new Set() } = {}) {
|
|
|
97
85
|
}
|
|
98
86
|
|
|
99
87
|
async function readPortFromEnvFile(envPath) {
|
|
100
|
-
|
|
101
|
-
if (!raw.trim()) return null;
|
|
102
|
-
const parsed = parseEnvToObject(raw);
|
|
103
|
-
const portRaw = (parsed.HAPPY_STACKS_SERVER_PORT ?? parsed.HAPPY_LOCAL_SERVER_PORT ?? '').toString().trim();
|
|
104
|
-
const n = portRaw ? Number(portRaw) : NaN;
|
|
105
|
-
return Number.isFinite(n) && n > 0 ? n : null;
|
|
88
|
+
return await readPinnedServerPortFromEnvFile(envPath);
|
|
106
89
|
}
|
|
107
90
|
|
|
108
91
|
async function readPortsFromEnvFile(envPath) {
|
|
109
92
|
const raw = await readExistingEnv(envPath);
|
|
110
93
|
if (!raw.trim()) return [];
|
|
111
94
|
const parsed = parseEnvToObject(raw);
|
|
112
|
-
|
|
113
|
-
'HAPPY_STACKS_SERVER_PORT',
|
|
114
|
-
'HAPPY_LOCAL_SERVER_PORT',
|
|
115
|
-
'HAPPY_STACKS_HAPPY_SERVER_BACKEND_PORT',
|
|
116
|
-
'HAPPY_STACKS_PG_PORT',
|
|
117
|
-
'HAPPY_STACKS_REDIS_PORT',
|
|
118
|
-
'HAPPY_STACKS_MINIO_PORT',
|
|
119
|
-
'HAPPY_STACKS_MINIO_CONSOLE_PORT',
|
|
120
|
-
];
|
|
121
|
-
const ports = [];
|
|
122
|
-
for (const k of keys) {
|
|
123
|
-
const rawV = (parsed[k] ?? '').toString().trim();
|
|
124
|
-
const n = rawV ? Number(rawV) : NaN;
|
|
125
|
-
if (Number.isFinite(n) && n > 0) ports.push(n);
|
|
126
|
-
}
|
|
127
|
-
return ports;
|
|
95
|
+
return listPortsFromEnvObject(parsed, STACK_RESERVED_PORT_KEYS);
|
|
128
96
|
}
|
|
129
97
|
|
|
130
98
|
async function collectReservedStackPorts({ excludeStackName = null } = {}) {
|
|
@@ -159,54 +127,7 @@ async function collectReservedStackPorts({ excludeStackName = null } = {}) {
|
|
|
159
127
|
return reserved;
|
|
160
128
|
}
|
|
161
129
|
|
|
162
|
-
|
|
163
|
-
return Buffer.from(buf)
|
|
164
|
-
.toString('base64')
|
|
165
|
-
.replaceAll('+', '-')
|
|
166
|
-
.replaceAll('/', '_')
|
|
167
|
-
.replaceAll('=', '');
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function randomToken(lenBytes = 24) {
|
|
171
|
-
return base64Url(randomBytes(lenBytes));
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function sanitizeDnsLabel(raw, { fallback = 'happy' } = {}) {
|
|
175
|
-
const s = String(raw ?? '')
|
|
176
|
-
.toLowerCase()
|
|
177
|
-
.replace(/[^a-z0-9-]+/g, '-')
|
|
178
|
-
.replace(/-+/g, '-')
|
|
179
|
-
.replace(/^-+/, '')
|
|
180
|
-
.replace(/-+$/, '');
|
|
181
|
-
return s || fallback;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
async function ensureDir(p) {
|
|
185
|
-
await mkdir(p, { recursive: true });
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
async function readTextIfExists(path) {
|
|
189
|
-
try {
|
|
190
|
-
if (!existsSync(path)) return null;
|
|
191
|
-
const raw = await readFile(path, 'utf-8');
|
|
192
|
-
const t = raw.trim();
|
|
193
|
-
return t ? t : null;
|
|
194
|
-
} catch {
|
|
195
|
-
return null;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// auth file copy/link helpers live in scripts/utils/auth_files.mjs
|
|
200
|
-
|
|
201
|
-
function getCliHomeDirFromEnvOrDefault({ stackBaseDir, env }) {
|
|
202
|
-
const fromEnv = (env.HAPPY_STACKS_CLI_HOME_DIR ?? env.HAPPY_LOCAL_CLI_HOME_DIR ?? '').trim();
|
|
203
|
-
return fromEnv || join(stackBaseDir, 'cli');
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function getServerLightDataDirFromEnvOrDefault({ stackBaseDir, env }) {
|
|
207
|
-
const fromEnv = (env.HAPPY_SERVER_LIGHT_DATA_DIR ?? '').trim();
|
|
208
|
-
return fromEnv || join(stackBaseDir, 'server-light');
|
|
209
|
-
}
|
|
130
|
+
// auth file copy/link helpers live in scripts/utils/auth/files.mjs
|
|
210
131
|
|
|
211
132
|
async function copyAuthFromStackIntoNewStack({
|
|
212
133
|
fromStackName,
|
|
@@ -255,8 +176,8 @@ async function copyAuthFromStackIntoNewStack({
|
|
|
255
176
|
'If you really want this, set: HAPPY_STACKS_SANDBOX_ALLOW_GLOBAL=1'
|
|
256
177
|
);
|
|
257
178
|
}
|
|
258
|
-
const sourceBaseDir = legacy ? getLegacyHappyBaseDir() :
|
|
259
|
-
const sourceEnvRaw = legacy ? '' : await readExistingEnv(
|
|
179
|
+
const sourceBaseDir = legacy ? getLegacyHappyBaseDir() : resolveStackEnvPath(fromStackName).baseDir;
|
|
180
|
+
const sourceEnvRaw = legacy ? '' : await readExistingEnv(resolveStackEnvPath(fromStackName).envPath);
|
|
260
181
|
const sourceEnv = parseEnvToObject(sourceEnvRaw);
|
|
261
182
|
const sourceCli = legacy ? join(sourceBaseDir, 'cli') : getCliHomeDirFromEnvOrDefault({ stackBaseDir: sourceBaseDir, env: sourceEnv });
|
|
262
183
|
const targetCli = stackEnv.HAPPY_STACKS_CLI_HOME_DIR;
|
|
@@ -302,25 +223,7 @@ function stringifyEnv(env) {
|
|
|
302
223
|
return lines.join('\n') + '\n';
|
|
303
224
|
}
|
|
304
225
|
|
|
305
|
-
|
|
306
|
-
try {
|
|
307
|
-
const raw = await readFile(path, 'utf-8');
|
|
308
|
-
return raw;
|
|
309
|
-
} catch {
|
|
310
|
-
return '';
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
function parseEnvToObject(raw) {
|
|
315
|
-
const parsed = parseDotenv(raw);
|
|
316
|
-
return Object.fromEntries(parsed.entries());
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
function stackExistsSync(stackName) {
|
|
320
|
-
if (stackName === 'main') return true;
|
|
321
|
-
const envPath = getStackEnvPath(stackName);
|
|
322
|
-
return existsSync(envPath);
|
|
323
|
-
}
|
|
226
|
+
const readExistingEnv = readTextOrEmpty;
|
|
324
227
|
|
|
325
228
|
function resolveDefaultComponentDirs({ rootDir }) {
|
|
326
229
|
const componentNames = ['happy', 'happy-cli', 'happy-server-light', 'happy-server'];
|
|
@@ -335,9 +238,9 @@ function resolveDefaultComponentDirs({ rootDir }) {
|
|
|
335
238
|
}
|
|
336
239
|
|
|
337
240
|
async function writeStackEnv({ stackName, env }) {
|
|
338
|
-
const stackDir =
|
|
241
|
+
const stackDir = resolveStackEnvPath(stackName).baseDir;
|
|
339
242
|
await ensureDir(stackDir);
|
|
340
|
-
const envPath =
|
|
243
|
+
const envPath = resolveStackEnvPath(stackName).envPath;
|
|
341
244
|
const next = stringifyEnv(env);
|
|
342
245
|
const existing = await readExistingEnv(envPath);
|
|
343
246
|
if (existing !== next) {
|
|
@@ -347,7 +250,7 @@ async function writeStackEnv({ stackName, env }) {
|
|
|
347
250
|
}
|
|
348
251
|
|
|
349
252
|
async function withStackEnv({ stackName, fn, extraEnv = {} }) {
|
|
350
|
-
const envPath =
|
|
253
|
+
const envPath = resolveStackEnvPath(stackName).envPath;
|
|
351
254
|
if (!stackExistsSync(stackName)) {
|
|
352
255
|
throw new Error(
|
|
353
256
|
`[stack] stack "${stackName}" does not exist yet.\n` +
|
|
@@ -604,7 +507,7 @@ async function cmdNew({ rootDir, argv, emit = true }) {
|
|
|
604
507
|
throw new Error(`[stack] invalid server component: ${serverComponent}`);
|
|
605
508
|
}
|
|
606
509
|
|
|
607
|
-
const baseDir =
|
|
510
|
+
const baseDir = resolveStackEnvPath(stackName).baseDir;
|
|
608
511
|
const uiBuildDir = join(baseDir, 'ui');
|
|
609
512
|
const cliHomeDir = join(baseDir, 'cli');
|
|
610
513
|
|
|
@@ -804,7 +707,7 @@ async function cmdEdit({ rootDir, argv }) {
|
|
|
804
707
|
throw new Error('[stack] usage: happys stack edit <name> [--interactive]');
|
|
805
708
|
}
|
|
806
709
|
|
|
807
|
-
const envPath =
|
|
710
|
+
const envPath = resolveStackEnvPath(stackName).envPath;
|
|
808
711
|
const raw = await readExistingEnv(envPath);
|
|
809
712
|
const existingEnv = parseEnvToObject(raw);
|
|
810
713
|
|
|
@@ -829,7 +732,7 @@ async function cmdEdit({ rootDir, argv }) {
|
|
|
829
732
|
const config = await withRl((rl) => interactiveEdit({ rootDir, rl, stackName, existingEnv, defaults }));
|
|
830
733
|
|
|
831
734
|
// Build next env, starting from existing env but enforcing stack-scoped invariants.
|
|
832
|
-
const baseDir =
|
|
735
|
+
const baseDir = resolveStackEnvPath(stackName).baseDir;
|
|
833
736
|
const uiBuildDir = join(baseDir, 'ui');
|
|
834
737
|
const cliHomeDir = join(baseDir, 'cli');
|
|
835
738
|
|
|
@@ -1894,7 +1797,7 @@ async function cmdCreateDevAuthSeed({ rootDir, argv }) {
|
|
|
1894
1797
|
const internalServerUrl = `http://127.0.0.1:${serverPort}`;
|
|
1895
1798
|
const publicServerUrl = `http://localhost:${serverPort}`;
|
|
1896
1799
|
|
|
1897
|
-
const autostart = { stackName: name, baseDir:
|
|
1800
|
+
const autostart = { stackName: name, baseDir: resolveStackEnvPath(name).baseDir };
|
|
1898
1801
|
const children = [];
|
|
1899
1802
|
|
|
1900
1803
|
await withStackEnv({
|
|
@@ -1962,7 +1865,7 @@ async function cmdCreateDevAuthSeed({ rootDir, argv }) {
|
|
|
1962
1865
|
}
|
|
1963
1866
|
|
|
1964
1867
|
console.log('');
|
|
1965
|
-
const uiHost =
|
|
1868
|
+
const uiHost = resolveLocalhostHost({ stackMode: true, stackName: name });
|
|
1966
1869
|
const uiPort = uiRes?.port;
|
|
1967
1870
|
const uiRoot = Number.isFinite(uiPort) && uiPort > 0 ? `http://${uiHost}:${uiPort}` : null;
|
|
1968
1871
|
const uiRootLocalhost = Number.isFinite(uiPort) && uiPort > 0 ? `http://localhost:${uiPort}` : null;
|
|
@@ -2092,7 +1995,7 @@ function parseServerComponentFromEnv(env) {
|
|
|
2092
1995
|
}
|
|
2093
1996
|
|
|
2094
1997
|
async function readStackEnvObject(stackName) {
|
|
2095
|
-
const envPath =
|
|
1998
|
+
const envPath = resolveStackEnvPath(stackName).envPath;
|
|
2096
1999
|
const raw = await readExistingEnv(envPath);
|
|
2097
2000
|
const env = raw ? parseEnvToObject(raw) : {};
|
|
2098
2001
|
return { envPath, env };
|
|
@@ -2107,16 +2010,6 @@ function envKeyForComponentDir({ serverComponent, component }) {
|
|
|
2107
2010
|
return `HAPPY_STACKS_COMPONENT_DIR_${component.toUpperCase().replace(/[^A-Z0-9]+/g, '_')}`;
|
|
2108
2011
|
}
|
|
2109
2012
|
|
|
2110
|
-
function sanitizeSlugPart(s) {
|
|
2111
|
-
return String(s ?? '')
|
|
2112
|
-
.trim()
|
|
2113
|
-
.toLowerCase()
|
|
2114
|
-
.replace(/[^a-z0-9._/-]+/g, '-')
|
|
2115
|
-
.replace(/-+/g, '-')
|
|
2116
|
-
.replace(/^-+/, '')
|
|
2117
|
-
.replace(/-+$/, '');
|
|
2118
|
-
}
|
|
2119
|
-
|
|
2120
2013
|
async function cmdDuplicate({ rootDir, argv }) {
|
|
2121
2014
|
const { flags, kv } = parseArgs(argv);
|
|
2122
2015
|
const json = wantsJson(argv, { flags });
|
|
@@ -2190,7 +2083,7 @@ async function cmdDuplicate({ rootDir, argv }) {
|
|
|
2190
2083
|
}
|
|
2191
2084
|
|
|
2192
2085
|
// Apply component dir overrides to the destination stack env file.
|
|
2193
|
-
const toEnvPath =
|
|
2086
|
+
const toEnvPath = resolveStackEnvPath(toStack).envPath;
|
|
2194
2087
|
if (updates.length) {
|
|
2195
2088
|
await ensureEnvFileUpdated({ envPath: toEnvPath, updates });
|
|
2196
2089
|
}
|
|
@@ -2577,8 +2470,8 @@ async function cmdPrStack({ rootDir, argv }) {
|
|
|
2577
2470
|
|
|
2578
2471
|
async function cmdInfoInternal({ rootDir, stackName }) {
|
|
2579
2472
|
// Minimal extraction from cmdInfo to avoid re-parsing argv/printing. Used by cmdPrStack.
|
|
2580
|
-
const baseDir =
|
|
2581
|
-
const envPath =
|
|
2473
|
+
const baseDir = resolveStackEnvPath(stackName).baseDir;
|
|
2474
|
+
const envPath = resolveStackEnvPath(stackName).envPath;
|
|
2582
2475
|
const envRaw = await readExistingEnv(envPath);
|
|
2583
2476
|
const stackEnv = envRaw ? parseEnvToObject(envRaw) : {};
|
|
2584
2477
|
const runtimeStatePath = getStackRuntimeStatePath(stackName);
|
|
@@ -2587,6 +2480,9 @@ async function cmdInfoInternal({ rootDir, stackName }) {
|
|
|
2587
2480
|
const serverComponent =
|
|
2588
2481
|
getEnvValueAny(stackEnv, ['HAPPY_STACKS_SERVER_COMPONENT', 'HAPPY_LOCAL_SERVER_COMPONENT']) || 'happy-server-light';
|
|
2589
2482
|
|
|
2483
|
+
const stackRemote =
|
|
2484
|
+
getEnvValueAny(stackEnv, ['HAPPY_STACKS_STACK_REMOTE', 'HAPPY_LOCAL_STACK_REMOTE']) || 'upstream';
|
|
2485
|
+
|
|
2590
2486
|
const pinnedServerPortRaw = getEnvValueAny(stackEnv, ['HAPPY_STACKS_SERVER_PORT', 'HAPPY_LOCAL_SERVER_PORT']);
|
|
2591
2487
|
const pinnedServerPort = pinnedServerPortRaw ? Number(pinnedServerPortRaw) : null;
|
|
2592
2488
|
|
|
@@ -2632,6 +2528,7 @@ async function cmdInfoInternal({ rootDir, stackName }) {
|
|
|
2632
2528
|
envPath,
|
|
2633
2529
|
runtimeStatePath,
|
|
2634
2530
|
serverComponent,
|
|
2531
|
+
stackRemote,
|
|
2635
2532
|
pinned: {
|
|
2636
2533
|
serverPort: Number.isFinite(pinnedServerPort) && pinnedServerPort > 0 ? pinnedServerPort : null,
|
|
2637
2534
|
},
|
|
@@ -2659,6 +2556,31 @@ async function cmdInfoInternal({ rootDir, stackName }) {
|
|
|
2659
2556
|
};
|
|
2660
2557
|
}
|
|
2661
2558
|
|
|
2559
|
+
async function cmdStackCodeOrCursor({ rootDir, stackName, json, editor, includeStackDir, includeAllComponents, includeCliHome }) {
|
|
2560
|
+
const ws = await writeStackCodeWorkspace({ rootDir, stackName, includeStackDir, includeAllComponents, includeCliHome });
|
|
2561
|
+
|
|
2562
|
+
if (json) {
|
|
2563
|
+
printResult({
|
|
2564
|
+
json,
|
|
2565
|
+
data: {
|
|
2566
|
+
ok: true,
|
|
2567
|
+
stackName,
|
|
2568
|
+
editor,
|
|
2569
|
+
...ws,
|
|
2570
|
+
},
|
|
2571
|
+
});
|
|
2572
|
+
return;
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
await openWorkspaceInEditor({ rootDir, editor, workspacePath: ws.workspacePath });
|
|
2576
|
+
console.log(`[stack] opened ${editor === 'code' ? 'VS Code' : 'Cursor'} workspace for "${stackName}": ${ws.workspacePath}`);
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
async function cmdStackOpen({ rootDir, stackName, json, includeStackDir, includeAllComponents, includeCliHome }) {
|
|
2580
|
+
const editor = (await isCursorInstalled({ cwd: rootDir, env: process.env })) ? 'cursor' : 'code';
|
|
2581
|
+
await cmdStackCodeOrCursor({ rootDir, stackName, json, editor, includeStackDir, includeAllComponents, includeCliHome });
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2662
2584
|
async function main() {
|
|
2663
2585
|
const rootDir = getRootDir(import.meta.url);
|
|
2664
2586
|
// pnpm (legacy) passes an extra leading `--` when forwarding args into scripts. Normalize it away so
|
|
@@ -2702,6 +2624,9 @@ async function main() {
|
|
|
2702
2624
|
'mobile',
|
|
2703
2625
|
'resume',
|
|
2704
2626
|
'stop',
|
|
2627
|
+
'code',
|
|
2628
|
+
'cursor',
|
|
2629
|
+
'open',
|
|
2705
2630
|
'srv',
|
|
2706
2631
|
'wt',
|
|
2707
2632
|
'tailscale:*',
|
|
@@ -2730,6 +2655,9 @@ async function main() {
|
|
|
2730
2655
|
' happys stack mobile <name> [-- ...]',
|
|
2731
2656
|
' happys stack resume <name> <sessionId...> [--json]',
|
|
2732
2657
|
' happys stack stop <name> [--aggressive] [--sweep-owned] [--no-docker] [--json]',
|
|
2658
|
+
' happys stack code <name> [--no-stack-dir] [--include-all-components] [--include-cli-home] [--json]',
|
|
2659
|
+
' happys stack cursor <name> [--no-stack-dir] [--include-all-components] [--include-cli-home] [--json]',
|
|
2660
|
+
' happys stack open <name> [--no-stack-dir] [--include-all-components] [--include-cli-home] [--json] # prefer Cursor, else VS Code',
|
|
2733
2661
|
' happys stack srv <name> -- status|use ...',
|
|
2734
2662
|
' happys stack wt <name> -- <wt args...>',
|
|
2735
2663
|
' happys stack tailscale:status|enable|disable|url <name> [-- ...]',
|
|
@@ -2928,7 +2856,7 @@ async function main() {
|
|
|
2928
2856
|
const noDocker = stopFlags.has('--no-docker');
|
|
2929
2857
|
const aggressive = stopFlags.has('--aggressive');
|
|
2930
2858
|
const sweepOwned = stopFlags.has('--sweep-owned');
|
|
2931
|
-
const baseDir =
|
|
2859
|
+
const baseDir = resolveStackEnvPath(stackName).baseDir;
|
|
2932
2860
|
const out = await withStackEnv({
|
|
2933
2861
|
stackName,
|
|
2934
2862
|
fn: async ({ env }) => {
|
|
@@ -2939,6 +2867,28 @@ async function main() {
|
|
|
2939
2867
|
return;
|
|
2940
2868
|
}
|
|
2941
2869
|
|
|
2870
|
+
if (cmd === 'code') {
|
|
2871
|
+
const includeStackDir = !flags.has('--no-stack-dir');
|
|
2872
|
+
const includeAllComponents = flags.has('--include-all-components');
|
|
2873
|
+
const includeCliHome = flags.has('--include-cli-home');
|
|
2874
|
+
await cmdStackCodeOrCursor({ rootDir, stackName, json, editor: 'code', includeStackDir, includeAllComponents, includeCliHome });
|
|
2875
|
+
return;
|
|
2876
|
+
}
|
|
2877
|
+
if (cmd === 'cursor') {
|
|
2878
|
+
const includeStackDir = !flags.has('--no-stack-dir');
|
|
2879
|
+
const includeAllComponents = flags.has('--include-all-components');
|
|
2880
|
+
const includeCliHome = flags.has('--include-cli-home');
|
|
2881
|
+
await cmdStackCodeOrCursor({ rootDir, stackName, json, editor: 'cursor', includeStackDir, includeAllComponents, includeCliHome });
|
|
2882
|
+
return;
|
|
2883
|
+
}
|
|
2884
|
+
if (cmd === 'open') {
|
|
2885
|
+
const includeStackDir = !flags.has('--no-stack-dir');
|
|
2886
|
+
const includeAllComponents = flags.has('--include-all-components');
|
|
2887
|
+
const includeCliHome = flags.has('--include-cli-home');
|
|
2888
|
+
await cmdStackOpen({ rootDir, stackName, json, includeStackDir, includeAllComponents, includeCliHome });
|
|
2889
|
+
return;
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2942
2892
|
if (cmd === 'srv') {
|
|
2943
2893
|
await cmdSrv({ rootDir, stackName, args: passthrough });
|
|
2944
2894
|
return;
|
package/scripts/stop.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import './utils/env.mjs';
|
|
1
|
+
import './utils/env/env.mjs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { existsSync } from 'node:fs';
|
|
4
4
|
|
|
5
5
|
import { parseArgs } from './utils/cli/args.mjs';
|
|
6
6
|
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
|
|
7
|
-
import { run, runCapture } from './utils/proc.mjs';
|
|
8
|
-
import { getRootDir, resolveStackEnvPath } from './utils/paths.mjs';
|
|
7
|
+
import { run, runCapture } from './utils/proc/proc.mjs';
|
|
8
|
+
import { getRootDir, resolveStackEnvPath } from './utils/paths/paths.mjs';
|
|
9
9
|
|
|
10
10
|
function usage() {
|
|
11
11
|
return [
|
package/scripts/tailscale.mjs
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import './utils/env.mjs';
|
|
1
|
+
import './utils/env/env.mjs';
|
|
2
2
|
import { parseArgs } from './utils/cli/args.mjs';
|
|
3
|
-
import { run, runCapture } from './utils/proc.mjs';
|
|
3
|
+
import { run, runCapture } from './utils/proc/proc.mjs';
|
|
4
4
|
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
|
|
5
|
-
import { isSandboxed, sandboxAllowsGlobalSideEffects } from './utils/sandbox.mjs';
|
|
5
|
+
import { isSandboxed, sandboxAllowsGlobalSideEffects } from './utils/env/sandbox.mjs';
|
|
6
|
+
import { getInternalServerUrl } from './utils/server/urls.mjs';
|
|
7
|
+
import { resolveCommandPath } from './utils/proc/commands.mjs';
|
|
6
8
|
import { constants } from 'node:fs';
|
|
7
9
|
import { access } from 'node:fs/promises';
|
|
8
10
|
|
|
@@ -21,11 +23,6 @@ import { access } from 'node:fs/promises';
|
|
|
21
23
|
* - url (print the first https:// URL from status output)
|
|
22
24
|
*/
|
|
23
25
|
|
|
24
|
-
function getInternalServerUrl() {
|
|
25
|
-
const port = process.env.HAPPY_LOCAL_SERVER_PORT?.trim() ? Number(process.env.HAPPY_LOCAL_SERVER_PORT) : 3005;
|
|
26
|
-
return `http://127.0.0.1:${port}`;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
26
|
function getServeConfig(internalServerUrl) {
|
|
30
27
|
const upstream = process.env.HAPPY_LOCAL_TAILSCALE_UPSTREAM?.trim()
|
|
31
28
|
? process.env.HAPPY_LOCAL_TAILSCALE_UPSTREAM.trim()
|
|
@@ -134,7 +131,7 @@ async function resolveTailscaleCmd() {
|
|
|
134
131
|
|
|
135
132
|
// Try PATH first (without executing `tailscale`, which can hang in some environments).
|
|
136
133
|
try {
|
|
137
|
-
const found =
|
|
134
|
+
const found = await resolveCommandPath('tailscale', { env: tailscaleEnv(), timeoutMs: tailscaleProbeTimeoutMs() });
|
|
138
135
|
if (found) {
|
|
139
136
|
return found;
|
|
140
137
|
}
|
|
@@ -342,7 +339,7 @@ async function main() {
|
|
|
342
339
|
return;
|
|
343
340
|
}
|
|
344
341
|
|
|
345
|
-
const internalServerUrl = getInternalServerUrl();
|
|
342
|
+
const internalServerUrl = getInternalServerUrl({ env: process.env, defaultPort: 3005 }).internalServerUrl;
|
|
346
343
|
if (flags.has('--upstream') || kv.get('--upstream')) {
|
|
347
344
|
process.env.HAPPY_LOCAL_TAILSCALE_UPSTREAM = kv.get('--upstream') ?? internalServerUrl;
|
|
348
345
|
}
|
package/scripts/test.mjs
CHANGED
|
@@ -1,36 +1,15 @@
|
|
|
1
|
-
import './utils/env.mjs';
|
|
1
|
+
import './utils/env/env.mjs';
|
|
2
2
|
import { parseArgs } from './utils/cli/args.mjs';
|
|
3
3
|
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
|
|
4
|
-
import { getComponentDir, getRootDir } from './utils/paths.mjs';
|
|
5
|
-
import { ensureDepsInstalled
|
|
6
|
-
import { pathExists } from './utils/fs.mjs';
|
|
7
|
-
import { run } from './utils/proc.mjs';
|
|
8
|
-
import {
|
|
9
|
-
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { getComponentDir, getRootDir } from './utils/paths/paths.mjs';
|
|
5
|
+
import { ensureDepsInstalled } from './utils/proc/pm.mjs';
|
|
6
|
+
import { pathExists } from './utils/fs/fs.mjs';
|
|
7
|
+
import { run } from './utils/proc/proc.mjs';
|
|
8
|
+
import { detectPackageManagerCmd, pickFirstScript, readPackageJsonScripts } from './utils/proc/package_scripts.mjs';
|
|
10
9
|
|
|
11
10
|
const DEFAULT_COMPONENTS = ['happy', 'happy-cli', 'happy-server-light', 'happy-server'];
|
|
12
11
|
|
|
13
|
-
async function detectPackageManagerCmd(dir) {
|
|
14
|
-
if (await pathExists(join(dir, 'yarn.lock'))) {
|
|
15
|
-
return { name: 'yarn', cmd: 'yarn', argsForScript: (script) => ['-s', script] };
|
|
16
|
-
}
|
|
17
|
-
await requirePnpm();
|
|
18
|
-
return { name: 'pnpm', cmd: 'pnpm', argsForScript: (script) => ['--silent', script] };
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async function readScripts(dir) {
|
|
22
|
-
try {
|
|
23
|
-
const raw = await readFile(join(dir, 'package.json'), 'utf-8');
|
|
24
|
-
const pkg = JSON.parse(raw);
|
|
25
|
-
const scripts = pkg?.scripts && typeof pkg.scripts === 'object' ? pkg.scripts : {};
|
|
26
|
-
return scripts;
|
|
27
|
-
} catch {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
12
|
function pickTestScript(scripts) {
|
|
33
|
-
if (!scripts) return null;
|
|
34
13
|
const candidates = [
|
|
35
14
|
'test',
|
|
36
15
|
'tst',
|
|
@@ -38,7 +17,7 @@ function pickTestScript(scripts) {
|
|
|
38
17
|
'test:unit',
|
|
39
18
|
'check:test',
|
|
40
19
|
];
|
|
41
|
-
return
|
|
20
|
+
return pickFirstScript(scripts, candidates);
|
|
42
21
|
}
|
|
43
22
|
|
|
44
23
|
async function main() {
|
|
@@ -85,7 +64,7 @@ async function main() {
|
|
|
85
64
|
continue;
|
|
86
65
|
}
|
|
87
66
|
|
|
88
|
-
const scripts = await
|
|
67
|
+
const scripts = await readPackageJsonScripts(dir);
|
|
89
68
|
if (!scripts) {
|
|
90
69
|
results.push({ component, ok: true, skipped: true, dir, reason: 'no package.json' });
|
|
91
70
|
continue;
|
package/scripts/tui.mjs
CHANGED
|
@@ -1,29 +1,13 @@
|
|
|
1
|
-
import './utils/env.mjs';
|
|
1
|
+
import './utils/env/env.mjs';
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
|
-
import { existsSync } from 'node:fs';
|
|
4
|
-
import { readFile } from 'node:fs/promises';
|
|
5
3
|
import { join, resolve, sep } from 'node:path';
|
|
6
4
|
|
|
7
|
-
import { parseDotenv } from './utils/dotenv.mjs';
|
|
8
5
|
import { printResult } from './utils/cli/cli.mjs';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return String(s ?? '').replace(/\x1b\[[0-9;]*[A-Za-z]/g, '');
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function padRight(s, n) {
|
|
18
|
-
const str = String(s ?? '');
|
|
19
|
-
if (str.length >= n) return str.slice(0, n);
|
|
20
|
-
return str + ' '.repeat(n - str.length);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function parsePrefixedLabel(line) {
|
|
24
|
-
const m = String(line ?? '').match(/^\[([^\]]+)\]\s*/);
|
|
25
|
-
return m ? m[1] : null;
|
|
26
|
-
}
|
|
6
|
+
import { readEnvObjectFromFile } from './utils/env/read.mjs';
|
|
7
|
+
import { getRootDir, resolveStackEnvPath } from './utils/paths/paths.mjs';
|
|
8
|
+
import { getStackRuntimeStatePath, readStackRuntimeStateFile } from './utils/stack/runtime_state.mjs';
|
|
9
|
+
import { getEnvValueAny } from './utils/env/values.mjs';
|
|
10
|
+
import { padRight, parsePrefixedLabel, stripAnsi } from './utils/ui/text.mjs';
|
|
27
11
|
|
|
28
12
|
function nowTs() {
|
|
29
13
|
const d = new Date();
|
|
@@ -107,15 +91,7 @@ function inferStackNameFromForwardedArgs(args) {
|
|
|
107
91
|
return (process.env.HAPPY_STACKS_STACK ?? process.env.HAPPY_LOCAL_STACK ?? '').trim() || 'main';
|
|
108
92
|
}
|
|
109
93
|
|
|
110
|
-
|
|
111
|
-
try {
|
|
112
|
-
if (!path || !existsSync(path)) return {};
|
|
113
|
-
const raw = await readFile(path, 'utf-8');
|
|
114
|
-
return Object.fromEntries(parseDotenv(raw).entries());
|
|
115
|
-
} catch {
|
|
116
|
-
return {};
|
|
117
|
-
}
|
|
118
|
-
}
|
|
94
|
+
const readEnvObject = readEnvObjectFromFile;
|
|
119
95
|
|
|
120
96
|
function formatComponentRef({ rootDir, component, dir }) {
|
|
121
97
|
const raw = String(dir ?? '').trim();
|
|
@@ -132,12 +108,6 @@ function formatComponentRef({ rootDir, component, dir }) {
|
|
|
132
108
|
return abs;
|
|
133
109
|
}
|
|
134
110
|
|
|
135
|
-
function getEnvVal(env, k1, k2) {
|
|
136
|
-
const a = String(env?.[k1] ?? '').trim();
|
|
137
|
-
if (a) return a;
|
|
138
|
-
return String(env?.[k2] ?? '').trim();
|
|
139
|
-
}
|
|
140
|
-
|
|
141
111
|
async function buildStackSummaryLines({ rootDir, stackName }) {
|
|
142
112
|
const { envPath, baseDir } = resolveStackEnvPath(stackName);
|
|
143
113
|
const env = await readEnvObject(envPath);
|
|
@@ -145,7 +115,7 @@ async function buildStackSummaryLines({ rootDir, stackName }) {
|
|
|
145
115
|
const runtime = await readStackRuntimeStateFile(runtimePath);
|
|
146
116
|
|
|
147
117
|
const serverComponent =
|
|
148
|
-
|
|
118
|
+
getEnvValueAny(env, ['HAPPY_STACKS_SERVER_COMPONENT', 'HAPPY_LOCAL_SERVER_COMPONENT']) || 'happy-server-light';
|
|
149
119
|
|
|
150
120
|
const ports = runtime?.ports && typeof runtime.ports === 'object' ? runtime.ports : {};
|
|
151
121
|
const expoWebPort = runtime?.expo && typeof runtime.expo === 'object' ? runtime.expo.webPort : null;
|