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.
Files changed (94) hide show
  1. package/README.md +59 -22
  2. package/bin/happys.mjs +2 -2
  3. package/package.json +1 -1
  4. package/scripts/auth.mjs +49 -202
  5. package/scripts/build.mjs +5 -6
  6. package/scripts/cli-link.mjs +3 -3
  7. package/scripts/completion.mjs +5 -5
  8. package/scripts/daemon.mjs +9 -17
  9. package/scripts/dev.mjs +18 -27
  10. package/scripts/doctor.mjs +20 -36
  11. package/scripts/edison.mjs +102 -77
  12. package/scripts/happy.mjs +8 -19
  13. package/scripts/init.mjs +5 -13
  14. package/scripts/install.mjs +8 -8
  15. package/scripts/lint.mjs +8 -29
  16. package/scripts/menubar.mjs +6 -13
  17. package/scripts/migrate.mjs +11 -21
  18. package/scripts/mobile.mjs +13 -12
  19. package/scripts/run.mjs +15 -15
  20. package/scripts/self.mjs +11 -29
  21. package/scripts/server_flavor.mjs +4 -4
  22. package/scripts/service.mjs +18 -28
  23. package/scripts/setup.mjs +26 -122
  24. package/scripts/setup_pr.mjs +11 -28
  25. package/scripts/stack.mjs +111 -161
  26. package/scripts/stop.mjs +3 -3
  27. package/scripts/tailscale.mjs +7 -10
  28. package/scripts/test.mjs +8 -29
  29. package/scripts/tui.mjs +8 -38
  30. package/scripts/typecheck.mjs +8 -29
  31. package/scripts/ui_gateway.mjs +1 -1
  32. package/scripts/uninstall.mjs +6 -6
  33. package/scripts/utils/{dev_auth_key.mjs → auth/dev_key.mjs} +2 -8
  34. package/scripts/utils/{auth_files.mjs → auth/files.mjs} +2 -4
  35. package/scripts/utils/{handy_master_secret.mjs → auth/handy_master_secret.mjs} +6 -32
  36. package/scripts/utils/cli/flags.mjs +17 -0
  37. package/scripts/utils/cli/normalize.mjs +16 -0
  38. package/scripts/utils/cli/smoke_help.mjs +2 -2
  39. package/scripts/utils/cli/wizard.mjs +1 -1
  40. package/scripts/utils/crypto/tokens.mjs +14 -0
  41. package/scripts/utils/{dev_daemon.mjs → dev/daemon.mjs} +4 -4
  42. package/scripts/utils/{dev_expo_web.mjs → dev/expo_web.mjs} +5 -5
  43. package/scripts/utils/{dev_server.mjs → dev/server.mjs} +7 -7
  44. package/scripts/utils/{config.mjs → env/config.mjs} +3 -2
  45. package/scripts/utils/{dotenv.mjs → env/dotenv.mjs} +3 -0
  46. package/scripts/utils/{env.mjs → env/env.mjs} +5 -3
  47. package/scripts/utils/{env_file.mjs → env/env_file.mjs} +2 -1
  48. package/scripts/utils/{env_local.mjs → env/env_local.mjs} +1 -0
  49. package/scripts/utils/env/read.mjs +30 -0
  50. package/scripts/utils/env/values.mjs +13 -0
  51. package/scripts/utils/{expo.mjs → expo/expo.mjs} +3 -9
  52. package/scripts/utils/fs/json.mjs +25 -0
  53. package/scripts/utils/fs/ops.mjs +29 -0
  54. package/scripts/utils/fs/package_json.mjs +8 -0
  55. package/scripts/utils/fs/tail.mjs +12 -0
  56. package/scripts/utils/git/refs.mjs +26 -0
  57. package/scripts/utils/{worktrees.mjs → git/worktrees.mjs} +3 -3
  58. package/scripts/utils/net/dns.mjs +10 -0
  59. package/scripts/utils/{ports.mjs → net/ports.mjs} +3 -5
  60. package/scripts/utils/{localhost_host.mjs → paths/localhost_host.mjs} +2 -10
  61. package/scripts/utils/{paths.mjs → paths/paths.mjs} +10 -7
  62. package/scripts/utils/{runtime.mjs → paths/runtime.mjs} +3 -1
  63. package/scripts/utils/proc/commands.mjs +34 -0
  64. package/scripts/utils/{ownership.mjs → proc/ownership.mjs} +1 -1
  65. package/scripts/utils/proc/package_scripts.mjs +31 -0
  66. package/scripts/utils/proc/pids.mjs +11 -0
  67. package/scripts/utils/{pm.mjs → proc/pm.mjs} +65 -152
  68. package/scripts/utils/{proc.mjs → proc/proc.mjs} +1 -0
  69. package/scripts/utils/{happy_server_infra.mjs → server/infra/happy_server_infra.mjs} +10 -49
  70. package/scripts/utils/server/port.mjs +68 -0
  71. package/scripts/utils/{server.mjs → server/server.mjs} +12 -0
  72. package/scripts/utils/server/urls.mjs +91 -0
  73. package/scripts/utils/{validate.mjs → server/validate.mjs} +1 -1
  74. package/scripts/utils/service/autostart_darwin.mjs +142 -0
  75. package/scripts/utils/{stack_context.mjs → stack/context.mjs} +2 -2
  76. package/scripts/utils/stack/dirs.mjs +27 -0
  77. package/scripts/utils/stack/editor_workspace.mjs +152 -0
  78. package/scripts/utils/stack/names.mjs +12 -0
  79. package/scripts/utils/{stack_runtime_state.mjs → stack/runtime_state.mjs} +10 -27
  80. package/scripts/utils/{stacks.mjs → stack/stacks.mjs} +9 -2
  81. package/scripts/utils/{stack_startup.mjs → stack/startup.mjs} +2 -2
  82. package/scripts/utils/{stack_stop.mjs → stack/stop.mjs} +9 -15
  83. package/scripts/utils/{browser.mjs → ui/browser.mjs} +1 -1
  84. package/scripts/utils/ui/text.mjs +16 -0
  85. package/scripts/where.mjs +6 -6
  86. package/scripts/worktrees.mjs +30 -58
  87. package/scripts/utils/server_port.mjs +0 -9
  88. package/scripts/utils/server_urls.mjs +0 -54
  89. /package/scripts/utils/{auth_login_ux.mjs → auth/login_ux.mjs} +0 -0
  90. /package/scripts/utils/{auth_sources.mjs → auth/sources.mjs} +0 -0
  91. /package/scripts/utils/{sandbox.mjs → env/sandbox.mjs} +0 -0
  92. /package/scripts/utils/{fs.mjs → fs/fs.mjs} +0 -0
  93. /package/scripts/utils/{canonical_home.mjs → paths/canonical_home.mjs} +0 -0
  94. /package/scripts/utils/{watch.mjs → proc/watch.mjs} +0 -0
@@ -1,18 +1,22 @@
1
- import './utils/env.mjs';
1
+ import './utils/env/env.mjs';
2
2
  import { mkdir, readFile, readdir, rm, symlink, writeFile } from 'node:fs/promises';
3
3
  import { dirname, isAbsolute, join, resolve } from 'node:path';
4
4
  import { parseArgs } from './utils/cli/args.mjs';
5
- import { pathExists } from './utils/fs.mjs';
6
- import { run, runCapture } from './utils/proc.mjs';
7
- import { componentDirEnvKey, getComponentDir, getComponentsDir, getHappyStacksHomeDir, getRootDir, getWorkspaceDir } from './utils/paths.mjs';
8
- import { inferRemoteNameForOwner, parseGithubOwner } from './utils/worktrees.mjs';
5
+ import { pathExists } from './utils/fs/fs.mjs';
6
+ import { run, runCapture } from './utils/proc/proc.mjs';
7
+ import { commandExists, resolveCommandPath } from './utils/proc/commands.mjs';
8
+ import { componentDirEnvKey, getComponentDir, getComponentsDir, getHappyStacksHomeDir, getRootDir, getWorkspaceDir } from './utils/paths/paths.mjs';
9
+ import { inferRemoteNameForOwner, parseGithubOwner } from './utils/git/worktrees.mjs';
10
+ import { getWorktreesRoot } from './utils/git/worktrees.mjs';
11
+ import { parseGithubPullRequest, sanitizeSlugPart } from './utils/git/refs.mjs';
12
+ import { readTextIfExists } from './utils/fs/ops.mjs';
9
13
  import { isTty, prompt, promptSelect, withRl } from './utils/cli/wizard.mjs';
10
14
  import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
11
- import { ensureEnvLocalUpdated } from './utils/env_local.mjs';
12
- import { ensureEnvFileUpdated } from './utils/env_file.mjs';
15
+ import { ensureEnvLocalUpdated } from './utils/env/env_local.mjs';
16
+ import { ensureEnvFileUpdated } from './utils/env/env_file.mjs';
13
17
  import { existsSync } from 'node:fs';
14
- import { getHomeEnvLocalPath, getHomeEnvPath, resolveUserConfigEnvPath } from './utils/config.mjs';
15
- import { detectServerComponentDirMismatch } from './utils/validate.mjs';
18
+ import { getHomeEnvLocalPath, getHomeEnvPath, resolveUserConfigEnvPath } from './utils/env/config.mjs';
19
+ import { detectServerComponentDirMismatch } from './utils/server/validate.mjs';
16
20
 
17
21
  function getActiveStackName() {
18
22
  return (process.env.HAPPY_STACKS_STACK ?? process.env.HAPPY_LOCAL_STACK ?? '').trim() || 'main';
@@ -22,10 +26,6 @@ function isMainStack() {
22
26
  return getActiveStackName() === 'main';
23
27
  }
24
28
 
25
- function getWorktreesRoot(rootDir) {
26
- return join(getComponentsDir(rootDir), '.worktrees');
27
- }
28
-
29
29
  function resolveComponentWorktreeDir({ rootDir, component, spec }) {
30
30
  const worktreesRoot = getWorktreesRoot(rootDir);
31
31
  const raw = (spec ?? '').trim();
@@ -51,32 +51,6 @@ function resolveComponentWorktreeDir({ rootDir, component, spec }) {
51
51
  return join(worktreesRoot, component, ...raw.split('/'));
52
52
  }
53
53
 
54
- function parseGithubPullRequest(input) {
55
- const raw = (input ?? '').trim();
56
- if (!raw) return null;
57
- if (/^\d+$/.test(raw)) {
58
- return { number: Number(raw), owner: null, repo: null };
59
- }
60
- // https://github.com/<owner>/<repo>/pull/<num>
61
- const m = raw.match(/github\.com\/(?<owner>[^/]+)\/(?<repo>[^/]+)\/pull\/(?<num>\d+)/);
62
- if (!m?.groups?.num) return null;
63
- return {
64
- number: Number(m.groups.num),
65
- owner: m.groups.owner ?? null,
66
- repo: m.groups.repo ?? null,
67
- };
68
- }
69
-
70
- function sanitizeSlugPart(s) {
71
- return (s ?? '')
72
- .toString()
73
- .trim()
74
- .toLowerCase()
75
- .replace(/[^a-z0-9._/-]+/g, '-')
76
- .replace(/-+/g, '-')
77
- .replace(/^-+|-+$/g, '');
78
- }
79
-
80
54
  async function isWorktreeClean(dir) {
81
55
  const dirty = (await git(dir, ['status', '--porcelain'])).trim();
82
56
  return !dirty;
@@ -469,13 +443,13 @@ async function cmdMigrate({ rootDir }) {
469
443
  // If the persisted config pins any component dir to a legacy location, attempt to rewrite it.
470
444
  const envUpdates = [];
471
445
 
472
- // Keep in sync with scripts/utils/env_local.mjs selection logic.
446
+ // Keep in sync with scripts/utils/env/env_local.mjs selection logic.
473
447
  const explicitEnv = (process.env.HAPPY_STACKS_ENV_FILE ?? process.env.HAPPY_LOCAL_ENV_FILE ?? '').trim();
474
448
  const hasHomeConfig = existsSync(getHomeEnvPath()) || existsSync(getHomeEnvLocalPath());
475
449
  const envPath = explicitEnv ? explicitEnv : hasHomeConfig ? resolveUserConfigEnvPath({ cliRootDir: rootDir }) : join(rootDir, 'env.local');
476
450
 
477
451
  if (await pathExists(envPath)) {
478
- const raw = await readFile(envPath, 'utf-8');
452
+ const raw = (await readTextIfExists(envPath)) ?? '';
479
453
  const rewrite = (v) => {
480
454
  if (!v.includes('/components/')) {
481
455
  return v;
@@ -1245,15 +1219,6 @@ async function cmdSync({ rootDir, argv }) {
1245
1219
  return { component, remote: remoteName, mirrorBranch, upstreamRef: `${remoteName}/${defaultBranch}` };
1246
1220
  }
1247
1221
 
1248
- async function commandExists(cmd) {
1249
- try {
1250
- const out = (await runCapture('sh', ['-lc', `command -v ${cmd} >/dev/null 2>&1 && echo yes || echo no`])).trim();
1251
- return out === 'yes';
1252
- } catch {
1253
- return false;
1254
- }
1255
- }
1256
-
1257
1222
  async function fileExists(path) {
1258
1223
  try {
1259
1224
  return await pathExists(path);
@@ -1408,14 +1373,15 @@ async function cmdCode({ rootDir, argv }) {
1408
1373
  if (!(await pathExists(dir))) {
1409
1374
  throw new Error(`[wt] target does not exist: ${dir}`);
1410
1375
  }
1411
- if (!(await commandExists('code'))) {
1376
+ const codePath = await resolveCommandPath('code', { cwd: rootDir, env: process.env });
1377
+ if (!codePath) {
1412
1378
  throw new Error("[wt] VS Code CLI 'code' not found on PATH. In VS Code: Cmd+Shift+P → 'Shell Command: Install code command in PATH'.");
1413
1379
  }
1414
1380
  if (json) {
1415
- return { component, dir, cmd: 'code' };
1381
+ return { component, dir, cmd: 'code', resolvedCmd: codePath };
1416
1382
  }
1417
- await run('code', [dir], { cwd: rootDir, env: process.env, stdio: 'inherit' });
1418
- return { component, dir, cmd: 'code' };
1383
+ await run(codePath, [dir], { cwd: rootDir, env: process.env, stdio: 'inherit' });
1384
+ return { component, dir, cmd: 'code', resolvedCmd: codePath };
1419
1385
  }
1420
1386
 
1421
1387
  async function cmdCursor({ rootDir, argv }) {
@@ -1432,14 +1398,20 @@ async function cmdCursor({ rootDir, argv }) {
1432
1398
  throw new Error(`[wt] target does not exist: ${dir}`);
1433
1399
  }
1434
1400
 
1435
- const hasCursorCli = await commandExists('cursor');
1401
+ const cursorPath = await resolveCommandPath('cursor', { cwd: rootDir, env: process.env });
1402
+ const hasCursorCli = Boolean(cursorPath);
1436
1403
  if (json) {
1437
- return { component, dir, cmd: hasCursorCli ? 'cursor' : process.platform === 'darwin' ? 'open -a Cursor' : null };
1404
+ return {
1405
+ component,
1406
+ dir,
1407
+ cmd: hasCursorCli ? 'cursor' : process.platform === 'darwin' ? 'open -a Cursor' : null,
1408
+ resolvedCmd: cursorPath || null,
1409
+ };
1438
1410
  }
1439
1411
 
1440
1412
  if (hasCursorCli) {
1441
- await run('cursor', [dir], { cwd: rootDir, env: process.env, stdio: 'inherit' });
1442
- return { component, dir, cmd: 'cursor' };
1413
+ await run(cursorPath, [dir], { cwd: rootDir, env: process.env, stdio: 'inherit' });
1414
+ return { component, dir, cmd: 'cursor', resolvedCmd: cursorPath };
1443
1415
  }
1444
1416
 
1445
1417
  if (process.platform === 'darwin') {
@@ -1,9 +0,0 @@
1
- export function resolveServerPortFromEnv({ env = process.env, defaultPort = 3005 } = {}) {
2
- const raw =
3
- (env.HAPPY_STACKS_SERVER_PORT ?? '').toString().trim() ||
4
- (env.HAPPY_LOCAL_SERVER_PORT ?? '').toString().trim() ||
5
- '';
6
- const n = raw ? Number(raw) : Number(defaultPort);
7
- return Number.isFinite(n) && n > 0 ? n : Number(defaultPort);
8
- }
9
-
@@ -1,54 +0,0 @@
1
- import { existsSync, readFileSync } from 'node:fs';
2
-
3
- import { getStackName, resolveStackEnvPath } from './paths.mjs';
4
- import { resolvePublicServerUrl } from '../tailscale.mjs';
5
- import { resolveServerPortFromEnv } from './server_port.mjs';
6
-
7
- function stackEnvExplicitlySetsPublicUrl({ env, stackName }) {
8
- try {
9
- const envPath =
10
- (env.HAPPY_STACKS_ENV_FILE ?? env.HAPPY_LOCAL_ENV_FILE ?? '').toString().trim() ||
11
- resolveStackEnvPath(stackName).envPath;
12
- if (!envPath || !existsSync(envPath)) return false;
13
- const raw = readFileSync(envPath, 'utf-8');
14
- return /^(HAPPY_STACKS_SERVER_URL|HAPPY_LOCAL_SERVER_URL)=/m.test(raw);
15
- } catch {
16
- return false;
17
- }
18
- }
19
-
20
- export function getPublicServerUrlEnvOverride({ env = process.env, serverPort } = {}) {
21
- const defaultPublicUrl = `http://localhost:${serverPort}`;
22
- const stackName = (env.HAPPY_STACKS_STACK ?? env.HAPPY_LOCAL_STACK ?? '').toString().trim() || getStackName();
23
-
24
- let envPublicUrl =
25
- (env.HAPPY_STACKS_SERVER_URL ?? env.HAPPY_LOCAL_SERVER_URL ?? '').toString().trim() || '';
26
-
27
- // Safety: for non-main stacks, ignore a global SERVER_URL unless it was explicitly set in the stack env file.
28
- if (stackName !== 'main' && envPublicUrl && !stackEnvExplicitlySetsPublicUrl({ env, stackName })) {
29
- envPublicUrl = '';
30
- }
31
-
32
- return { defaultPublicUrl, envPublicUrl, publicServerUrl: envPublicUrl || defaultPublicUrl };
33
- }
34
-
35
- export async function resolveServerUrls({ env = process.env, serverPort, allowEnable = true } = {}) {
36
- const internalServerUrl = `http://127.0.0.1:${serverPort}`;
37
- const { defaultPublicUrl, envPublicUrl } = getPublicServerUrlEnvOverride({ env, serverPort });
38
- const resolved = await resolvePublicServerUrl({
39
- internalServerUrl,
40
- defaultPublicUrl,
41
- envPublicUrl,
42
- allowEnable,
43
- });
44
- return {
45
- internalServerUrl,
46
- defaultPublicUrl,
47
- envPublicUrl,
48
- publicServerUrl: resolved.publicServerUrl,
49
- publicServerUrlSource: resolved.source,
50
- };
51
- }
52
-
53
- export { resolveServerPortFromEnv };
54
-
File without changes
File without changes
File without changes