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.
Files changed (149) hide show
  1. package/README.md +84 -25
  2. package/bin/happys.mjs +116 -17
  3. package/docs/happy-development.md +2 -2
  4. package/docs/isolated-linux-vm.md +82 -0
  5. package/docs/mobile-ios.md +112 -54
  6. package/package.json +5 -1
  7. package/scripts/auth.mjs +59 -208
  8. package/scripts/build.mjs +58 -12
  9. package/scripts/cli-link.mjs +3 -3
  10. package/scripts/completion.mjs +5 -5
  11. package/scripts/daemon.mjs +168 -20
  12. package/scripts/dev.mjs +196 -70
  13. package/scripts/doctor.mjs +20 -36
  14. package/scripts/edison.mjs +105 -78
  15. package/scripts/happy.mjs +8 -19
  16. package/scripts/init.mjs +8 -14
  17. package/scripts/install.mjs +119 -23
  18. package/scripts/lint.mjs +31 -32
  19. package/scripts/menubar.mjs +6 -13
  20. package/scripts/migrate.mjs +11 -21
  21. package/scripts/mobile.mjs +93 -108
  22. package/scripts/mobile_dev_client.mjs +83 -0
  23. package/scripts/provision/linux-ubuntu-review-pr.sh +51 -0
  24. package/scripts/review.mjs +217 -0
  25. package/scripts/review_pr.mjs +368 -0
  26. package/scripts/run.mjs +95 -21
  27. package/scripts/self.mjs +11 -29
  28. package/scripts/server_flavor.mjs +4 -4
  29. package/scripts/service.mjs +19 -29
  30. package/scripts/setup.mjs +63 -160
  31. package/scripts/setup_pr.mjs +592 -52
  32. package/scripts/stack.mjs +608 -200
  33. package/scripts/stop.mjs +3 -3
  34. package/scripts/tailscale.mjs +44 -11
  35. package/scripts/test.mjs +52 -36
  36. package/scripts/tui.mjs +314 -74
  37. package/scripts/typecheck.mjs +31 -32
  38. package/scripts/ui_gateway.mjs +1 -1
  39. package/scripts/uninstall.mjs +6 -6
  40. package/scripts/utils/auth/daemon_gate.mjs +55 -0
  41. package/scripts/utils/auth/daemon_gate.test.mjs +37 -0
  42. package/scripts/utils/auth/dev_key.mjs +163 -0
  43. package/scripts/utils/{auth_files.mjs → auth/files.mjs} +2 -4
  44. package/scripts/utils/auth/guided_pr_auth.mjs +79 -0
  45. package/scripts/utils/auth/guided_stack_web_login.mjs +75 -0
  46. package/scripts/utils/auth/handy_master_secret.mjs +68 -0
  47. package/scripts/utils/auth/interactive_stack_auth.mjs +72 -0
  48. package/scripts/utils/{auth_login_ux.mjs → auth/login_ux.mjs} +32 -13
  49. package/scripts/utils/auth/sources.mjs +38 -0
  50. package/scripts/utils/auth/stack_guided_login.mjs +353 -0
  51. package/scripts/utils/cli/cli_registry.mjs +24 -0
  52. package/scripts/utils/cli/cwd_scope.mjs +82 -0
  53. package/scripts/utils/cli/cwd_scope.test.mjs +77 -0
  54. package/scripts/utils/cli/flags.mjs +17 -0
  55. package/scripts/utils/cli/log_forwarder.mjs +157 -0
  56. package/scripts/utils/cli/normalize.mjs +16 -0
  57. package/scripts/utils/cli/prereqs.mjs +72 -0
  58. package/scripts/utils/cli/progress.mjs +126 -0
  59. package/scripts/utils/cli/smoke_help.mjs +2 -2
  60. package/scripts/utils/cli/verbosity.mjs +12 -0
  61. package/scripts/utils/cli/wizard.mjs +1 -1
  62. package/scripts/utils/crypto/tokens.mjs +14 -0
  63. package/scripts/utils/{dev_daemon.mjs → dev/daemon.mjs} +51 -7
  64. package/scripts/utils/dev/expo_dev.mjs +246 -0
  65. package/scripts/utils/dev/expo_dev.test.mjs +76 -0
  66. package/scripts/utils/{dev_server.mjs → dev/server.mjs} +22 -32
  67. package/scripts/utils/dev_auth_key.mjs +1 -1
  68. package/scripts/utils/{config.mjs → env/config.mjs} +3 -2
  69. package/scripts/utils/{dotenv.mjs → env/dotenv.mjs} +3 -0
  70. package/scripts/utils/{env.mjs → env/env.mjs} +5 -3
  71. package/scripts/utils/{env_file.mjs → env/env_file.mjs} +2 -1
  72. package/scripts/utils/{env_local.mjs → env/env_local.mjs} +1 -0
  73. package/scripts/utils/env/read.mjs +30 -0
  74. package/scripts/utils/env/values.mjs +13 -0
  75. package/scripts/utils/expo/command.mjs +52 -0
  76. package/scripts/utils/{expo.mjs → expo/expo.mjs} +23 -10
  77. package/scripts/utils/expo/metro_ports.mjs +114 -0
  78. package/scripts/utils/fs/json.mjs +25 -0
  79. package/scripts/utils/fs/ops.mjs +29 -0
  80. package/scripts/utils/fs/package_json.mjs +8 -0
  81. package/scripts/utils/fs/tail.mjs +12 -0
  82. package/scripts/utils/git/git.mjs +67 -0
  83. package/scripts/utils/git/refs.mjs +26 -0
  84. package/scripts/utils/{worktrees.mjs → git/worktrees.mjs} +27 -23
  85. package/scripts/utils/handy_master_secret.mjs +2 -2
  86. package/scripts/utils/mobile/config.mjs +31 -0
  87. package/scripts/utils/mobile/dev_client_links.mjs +60 -0
  88. package/scripts/utils/mobile/identifiers.mjs +47 -0
  89. package/scripts/utils/mobile/identifiers.test.mjs +42 -0
  90. package/scripts/utils/mobile/ios_xcodeproj_patch.mjs +128 -0
  91. package/scripts/utils/mobile/ios_xcodeproj_patch.test.mjs +98 -0
  92. package/scripts/utils/net/dns.mjs +10 -0
  93. package/scripts/utils/net/lan_ip.mjs +24 -0
  94. package/scripts/utils/{ports.mjs → net/ports.mjs} +12 -6
  95. package/scripts/utils/net/url.mjs +30 -0
  96. package/scripts/utils/net/url.test.mjs +20 -0
  97. package/scripts/utils/paths/localhost_host.mjs +56 -0
  98. package/scripts/utils/{paths.mjs → paths/paths.mjs} +52 -45
  99. package/scripts/utils/{runtime.mjs → paths/runtime.mjs} +3 -1
  100. package/scripts/utils/proc/commands.mjs +34 -0
  101. package/scripts/utils/{ownership.mjs → proc/ownership.mjs} +1 -1
  102. package/scripts/utils/proc/package_scripts.mjs +31 -0
  103. package/scripts/utils/proc/parallel.mjs +25 -0
  104. package/scripts/utils/proc/pids.mjs +11 -0
  105. package/scripts/utils/{pm.mjs → proc/pm.mjs} +128 -158
  106. package/scripts/utils/{proc.mjs → proc/proc.mjs} +77 -2
  107. package/scripts/utils/review/base_ref.mjs +74 -0
  108. package/scripts/utils/review/base_ref.test.mjs +54 -0
  109. package/scripts/utils/review/runners/coderabbit.mjs +19 -0
  110. package/scripts/utils/review/runners/codex.mjs +51 -0
  111. package/scripts/utils/review/targets.mjs +24 -0
  112. package/scripts/utils/review/targets.test.mjs +36 -0
  113. package/scripts/utils/sandbox/review_pr_sandbox.mjs +106 -0
  114. package/scripts/utils/{happy_server_infra.mjs → server/infra/happy_server_infra.mjs} +10 -49
  115. package/scripts/utils/server/mobile_api_url.mjs +61 -0
  116. package/scripts/utils/server/mobile_api_url.test.mjs +41 -0
  117. package/scripts/utils/server/port.mjs +68 -0
  118. package/scripts/utils/{server.mjs → server/server.mjs} +12 -0
  119. package/scripts/utils/server/urls.mjs +101 -0
  120. package/scripts/utils/server/validate.mjs +88 -0
  121. package/scripts/utils/service/autostart_darwin.mjs +182 -0
  122. package/scripts/utils/service/autostart_darwin.test.mjs +50 -0
  123. package/scripts/utils/stack/context.mjs +23 -0
  124. package/scripts/utils/stack/dirs.mjs +27 -0
  125. package/scripts/utils/stack/editor_workspace.mjs +152 -0
  126. package/scripts/utils/stack/names.mjs +12 -0
  127. package/scripts/utils/stack/pr_stack_name.mjs +16 -0
  128. package/scripts/utils/stack/runtime_state.mjs +88 -0
  129. package/scripts/utils/stack/stacks.mjs +45 -0
  130. package/scripts/utils/{stack_startup.mjs → stack/startup.mjs} +9 -2
  131. package/scripts/utils/{stack_stop.mjs → stack/stop.mjs} +24 -19
  132. package/scripts/utils/stack_context.mjs +3 -3
  133. package/scripts/utils/stack_runtime_state.mjs +1 -1
  134. package/scripts/utils/stacks.mjs +2 -2
  135. package/scripts/utils/{browser.mjs → ui/browser.mjs} +1 -1
  136. package/scripts/utils/ui/qr.mjs +17 -0
  137. package/scripts/utils/ui/text.mjs +16 -0
  138. package/scripts/utils/validate.mjs +1 -1
  139. package/scripts/where.mjs +6 -6
  140. package/scripts/worktrees.mjs +171 -113
  141. package/scripts/utils/auth_sources.mjs +0 -12
  142. package/scripts/utils/dev_expo_web.mjs +0 -112
  143. package/scripts/utils/localhost_host.mjs +0 -17
  144. package/scripts/utils/server_port.mjs +0 -9
  145. package/scripts/utils/server_urls.mjs +0 -54
  146. /package/scripts/utils/{sandbox.mjs → env/sandbox.mjs} +0 -0
  147. /package/scripts/utils/{fs.mjs → fs/fs.mjs} +0 -0
  148. /package/scripts/utils/{canonical_home.mjs → paths/canonical_home.mjs} +0 -0
  149. /package/scripts/utils/{watch.mjs → proc/watch.mjs} +0 -0
@@ -0,0 +1,45 @@
1
+ import { readdir } from 'node:fs/promises';
2
+ import { existsSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+
5
+ import { getLegacyStorageRoot, getStacksStorageRoot } from '../paths/paths.mjs';
6
+ import { isSandboxed, sandboxAllowsGlobalSideEffects } from '../env/sandbox.mjs';
7
+ import { resolveStackEnvPath } from '../paths/paths.mjs';
8
+
9
+ export async function listAllStackNames() {
10
+ const names = new Set(['main']);
11
+ const allowLegacy = !isSandboxed() || sandboxAllowsGlobalSideEffects();
12
+ const roots = [
13
+ // New layout: ~/.happy/stacks/<name>/env
14
+ getStacksStorageRoot(),
15
+ // Legacy layout: ~/.happy/local/stacks/<name>/env
16
+ ...(allowLegacy ? [join(getLegacyStorageRoot(), 'stacks')] : []),
17
+ ];
18
+
19
+ for (const root of roots) {
20
+ let entries = [];
21
+ try {
22
+ // eslint-disable-next-line no-await-in-loop
23
+ entries = await readdir(root, { withFileTypes: true });
24
+ } catch {
25
+ entries = [];
26
+ }
27
+ for (const ent of entries) {
28
+ if (!ent.isDirectory()) continue;
29
+ const name = ent.name;
30
+ if (!name || name.startsWith('.')) continue;
31
+ const envPath = join(root, name, 'env');
32
+ if (existsSync(envPath)) {
33
+ names.add(name);
34
+ }
35
+ }
36
+ }
37
+
38
+ return Array.from(names).sort();
39
+ }
40
+
41
+ export function stackExistsSync(stackName) {
42
+ const name = String(stackName ?? '').trim() || 'main';
43
+ if (name === 'main') return true;
44
+ return existsSync(resolveStackEnvPath(name).envPath);
45
+ }
@@ -1,5 +1,6 @@
1
- import { runCapture } from './proc.mjs';
2
- import { ensureDepsInstalled, pmExecBin } from './pm.mjs';
1
+ import { runCapture } from '../proc/proc.mjs';
2
+ import { ensureDepsInstalled, pmExecBin } from '../proc/pm.mjs';
3
+ import { isSandboxed, sandboxAllowsGlobalSideEffects } from '../env/sandbox.mjs';
3
4
  import { existsSync } from 'node:fs';
4
5
  import { join } from 'node:path';
5
6
 
@@ -47,6 +48,12 @@ async function probeAccountCount({ serverDir, env }) {
47
48
  }
48
49
 
49
50
  export function resolveAutoCopyFromMainEnabled({ env, stackName, isInteractive }) {
51
+ // Sandboxes should be isolated by default.
52
+ // Auto auth seeding can copy credentials/account rows from another stack (global state),
53
+ // which breaks isolation and can confuse guided auth flows (setup-pr/review-pr).
54
+ if (isSandboxed() && !sandboxAllowsGlobalSideEffects()) {
55
+ return false;
56
+ }
50
57
  const raw = (env.HAPPY_STACKS_AUTO_AUTH_SEED ?? env.HAPPY_LOCAL_AUTO_AUTH_SEED ?? '').toString().trim();
51
58
  if (raw) return raw !== '0';
52
59
 
@@ -2,19 +2,13 @@ import { existsSync } from 'node:fs';
2
2
  import { readdir, readFile } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
4
 
5
- import { getComponentDir } from './paths.mjs';
6
- import { isPidAlive, readPidState } from './expo.mjs';
7
- import { stopLocalDaemon } from '../daemon.mjs';
8
- import { stopHappyServerManagedInfra } from './happy_server_infra.mjs';
9
- import { deleteStackRuntimeStateFile, getStackRuntimeStatePath, readStackRuntimeStateFile } from './stack_runtime_state.mjs';
10
- import { killPidOwnedByStack, killProcessGroupOwnedByStack, listPidsWithEnvNeedle } from './ownership.mjs';
11
-
12
- function parseIntOrNull(raw) {
13
- const s = String(raw ?? '').trim();
14
- if (!s) return null;
15
- const n = Number(s);
16
- return Number.isFinite(n) && n > 0 ? n : null;
17
- }
5
+ import { getComponentDir } from '../paths/paths.mjs';
6
+ import { isPidAlive, readPidState } from '../expo/expo.mjs';
7
+ import { stopLocalDaemon } from '../../daemon.mjs';
8
+ import { stopHappyServerManagedInfra } from '../server/infra/happy_server_infra.mjs';
9
+ import { deleteStackRuntimeStateFile, getStackRuntimeStatePath, readStackRuntimeStateFile } from './runtime_state.mjs';
10
+ import { killPidOwnedByStack, killProcessGroupOwnedByStack, listPidsWithEnvNeedle } from '../proc/ownership.mjs';
11
+ import { coercePort } from '../server/port.mjs';
18
12
 
19
13
  function resolveServerComponentFromStackEnv(env) {
20
14
  const v =
@@ -132,6 +126,7 @@ export async function stopStackWithEnv({ rootDir, stackName, baseDir, env, json,
132
126
  daemonSessionsStopped: null,
133
127
  daemonStopped: false,
134
128
  killedPorts: [],
129
+ expoDev: [],
135
130
  uiDev: [],
136
131
  mobile: [],
137
132
  infra: null,
@@ -139,10 +134,16 @@ export async function stopStackWithEnv({ rootDir, stackName, baseDir, env, json,
139
134
  };
140
135
 
141
136
  const serverComponent = resolveServerComponentFromStackEnv(env);
142
- const port = parseIntOrNull(env.HAPPY_STACKS_SERVER_PORT ?? env.HAPPY_LOCAL_SERVER_PORT);
143
- const backendPort = parseIntOrNull(env.HAPPY_STACKS_HAPPY_SERVER_BACKEND_PORT ?? env.HAPPY_LOCAL_HAPPY_SERVER_BACKEND_PORT);
137
+ const port = coercePort(env.HAPPY_STACKS_SERVER_PORT ?? env.HAPPY_LOCAL_SERVER_PORT);
138
+ const backendPort = coercePort(env.HAPPY_STACKS_HAPPY_SERVER_BACKEND_PORT ?? env.HAPPY_LOCAL_HAPPY_SERVER_BACKEND_PORT);
144
139
  const cliHomeDir = (env.HAPPY_STACKS_CLI_HOME_DIR ?? env.HAPPY_LOCAL_CLI_HOME_DIR ?? join(baseDir, 'cli')).toString();
145
- const cliBin = join(getComponentDir(rootDir, 'happy-cli'), 'bin', 'happy.mjs');
140
+ // IMPORTANT:
141
+ // When stopping a stack, always prefer the stack's pinned happy-cli checkout/worktree.
142
+ // Otherwise, PR stacks can accidentally run the base checkout's CLI bin, which may not be built
143
+ // (we intentionally skip building base checkouts in some sandbox PR flows).
144
+ const pinnedCliDir = (env.HAPPY_STACKS_COMPONENT_DIR_HAPPY_CLI ?? env.HAPPY_LOCAL_COMPONENT_DIR_HAPPY_CLI ?? '').toString().trim();
145
+ const cliDir = pinnedCliDir || getComponentDir(rootDir, 'happy-cli');
146
+ const cliBin = join(cliDir, 'bin', 'happy.mjs');
146
147
  const envPath = (env.HAPPY_STACKS_ENV_FILE ?? env.HAPPY_LOCAL_ENV_FILE ?? '').toString();
147
148
 
148
149
  // Preferred: stop stack-started processes (by PID) recorded in stack.runtime.json.
@@ -200,12 +201,16 @@ export async function stopStackWithEnv({ rootDir, stackName, baseDir, env, json,
200
201
  }
201
202
 
202
203
  try {
203
- actions.uiDev = await stopExpoStateDir({ stackName, baseDir, kind: 'ui-dev', stateFileName: 'ui.state.json', envPath, json });
204
+ actions.expoDev = await stopExpoStateDir({ stackName, baseDir, kind: 'expo-dev', stateFileName: 'expo.state.json', envPath, json });
204
205
  } catch (e) {
205
- actions.errors.push({ step: 'expo-ui', error: e instanceof Error ? e.message : String(e) });
206
+ actions.errors.push({ step: 'expo-dev', error: e instanceof Error ? e.message : String(e) });
206
207
  }
207
208
  try {
208
- actions.mobile = await stopExpoStateDir({ stackName, baseDir, kind: 'mobile', stateFileName: 'expo.state.json', envPath, json });
209
+ // Legacy cleanups (best-effort): older runs used separate state dirs.
210
+ actions.uiDev = await stopExpoStateDir({ stackName, baseDir, kind: 'ui-dev', stateFileName: 'ui.state.json', envPath, json });
211
+ const killedDev = await stopExpoStateDir({ stackName, baseDir, kind: 'mobile-dev', stateFileName: 'mobile.state.json', envPath, json });
212
+ const killedLegacy = await stopExpoStateDir({ stackName, baseDir, kind: 'mobile', stateFileName: 'expo.state.json', envPath, json });
213
+ actions.mobile = [...killedDev, ...killedLegacy];
209
214
  } catch (e) {
210
215
  actions.errors.push({ step: 'expo-mobile', error: e instanceof Error ? e.message : String(e) });
211
216
  }
@@ -1,14 +1,14 @@
1
- import { getStackName, resolveStackEnvPath } from './paths.mjs';
1
+ import { getStackName, resolveStackEnvPath } from './paths/paths.mjs';
2
2
  import { getStackRuntimeStatePath } from './stack_runtime_state.mjs';
3
3
 
4
4
  export function resolveStackContext({ env = process.env, autostart = null } = {}) {
5
5
  const explicitStack = (env.HAPPY_STACKS_STACK ?? env.HAPPY_LOCAL_STACK ?? '').toString().trim();
6
- const stackName = explicitStack || (autostart?.stackName ?? '') || getStackName();
6
+ const stackName = explicitStack || (autostart?.stackName ?? '') || getStackName(env);
7
7
  const stackMode = Boolean(explicitStack);
8
8
 
9
9
  const envPath =
10
10
  (env.HAPPY_STACKS_ENV_FILE ?? env.HAPPY_LOCAL_ENV_FILE ?? '').toString().trim() ||
11
- resolveStackEnvPath(stackName).envPath;
11
+ resolveStackEnvPath(stackName, env).envPath;
12
12
 
13
13
  const runtimeStatePath =
14
14
  (env.HAPPY_STACKS_RUNTIME_STATE_PATH ?? env.HAPPY_LOCAL_RUNTIME_STATE_PATH ?? '').toString().trim() ||
@@ -2,7 +2,7 @@ import { existsSync } from 'node:fs';
2
2
  import { mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
3
3
  import { dirname, join } from 'node:path';
4
4
 
5
- import { resolveStackEnvPath } from './paths.mjs';
5
+ import { resolveStackEnvPath } from './paths/paths.mjs';
6
6
 
7
7
  export function getStackRuntimeStatePath(stackName) {
8
8
  const { baseDir } = resolveStackEnvPath(stackName);
@@ -2,8 +2,8 @@ import { readdir } from 'node:fs/promises';
2
2
  import { existsSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
4
 
5
- import { getLegacyStorageRoot, getStacksStorageRoot } from './paths.mjs';
6
- import { isSandboxed, sandboxAllowsGlobalSideEffects } from './sandbox.mjs';
5
+ import { getLegacyStorageRoot, getStacksStorageRoot } from './paths/paths.mjs';
6
+ import { isSandboxed, sandboxAllowsGlobalSideEffects } from './env/sandbox.mjs';
7
7
 
8
8
  export async function listAllStackNames() {
9
9
  const names = new Set(['main']);
@@ -1,4 +1,4 @@
1
- import { runCapture } from './proc.mjs';
1
+ import { runCapture } from '../proc/proc.mjs';
2
2
 
3
3
  export async function openUrlInBrowser(url, { timeoutMs = 5_000 } = {}) {
4
4
  const u = String(url ?? '').trim();
@@ -0,0 +1,17 @@
1
+ import qrcodeTerminal from 'qrcode-terminal';
2
+
3
+ export async function renderQrAscii(text, { small = true } = {}) {
4
+ const qrText = String(text ?? '');
5
+ if (!qrText) return { ok: false, lines: [], error: 'empty QR payload' };
6
+ try {
7
+ const out = await new Promise((resolvePromise) => {
8
+ qrcodeTerminal.generate(qrText, { small: Boolean(small) }, (qr) => resolvePromise(String(qr ?? '')));
9
+ });
10
+ // Important: keep whitespace; scanners rely on quiet-zone padding.
11
+ const lines = String(out ?? '').replace(/\r/g, '').split('\n');
12
+ return { ok: true, lines, error: null };
13
+ } catch (e) {
14
+ return { ok: false, lines: [], error: e instanceof Error ? e.message : String(e) };
15
+ }
16
+ }
17
+
@@ -0,0 +1,16 @@
1
+ export function stripAnsi(s) {
2
+ // eslint-disable-next-line no-control-regex
3
+ return String(s ?? '').replace(/\x1b\[[0-9;]*[A-Za-z]/g, '');
4
+ }
5
+
6
+ export function padRight(s, n) {
7
+ const str = String(s ?? '');
8
+ if (str.length >= n) return str.slice(0, n);
9
+ return str + ' '.repeat(n - str.length);
10
+ }
11
+
12
+ export function parsePrefixedLabel(line) {
13
+ const m = String(line ?? '').match(/^\[([^\]]+)\]\s*/);
14
+ return m ? m[1] : null;
15
+ }
16
+
@@ -1,6 +1,6 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { join, resolve, sep } from 'node:path';
3
- import { getComponentsDir } from './paths.mjs';
3
+ import { getComponentsDir } from './paths/paths.mjs';
4
4
 
5
5
  function isInside(path, dir) {
6
6
  const p = resolve(path);
package/scripts/where.mjs CHANGED
@@ -1,15 +1,15 @@
1
- import './utils/env.mjs';
1
+ import './utils/env/env.mjs';
2
2
 
3
3
  import { existsSync } from 'node:fs';
4
4
  import { join } from 'node:path';
5
5
 
6
6
  import { parseArgs } from './utils/cli/args.mjs';
7
- import { expandHome } from './utils/canonical_home.mjs';
8
- import { getComponentsDir, getComponentDir, getHappyStacksHomeDir, getRootDir, getStackLabel, getStackName, getWorkspaceDir, resolveStackEnvPath } from './utils/paths.mjs';
7
+ import { expandHome } from './utils/paths/canonical_home.mjs';
8
+ import { getComponentsDir, getComponentDir, getHappyStacksHomeDir, getRootDir, getStackLabel, getStackName, getWorkspaceDir, resolveStackEnvPath } from './utils/paths/paths.mjs';
9
9
  import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
10
- import { getRuntimeDir } from './utils/runtime.mjs';
11
- import { getCanonicalHomeDir, getCanonicalHomeEnvPath } from './utils/config.mjs';
12
- import { getSandboxDir } from './utils/sandbox.mjs';
10
+ import { getRuntimeDir } from './utils/paths/runtime.mjs';
11
+ import { getCanonicalHomeDir, getCanonicalHomeEnvPath } from './utils/env/config.mjs';
12
+ import { getSandboxDir } from './utils/env/sandbox.mjs';
13
13
 
14
14
  function getHomeEnvPaths() {
15
15
  const homeDir = getHappyStacksHomeDir();