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/worktrees.mjs
CHANGED
|
@@ -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 {
|
|
8
|
-
import {
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
|
1401
|
+
const cursorPath = await resolveCommandPath('cursor', { cwd: rootDir, env: process.env });
|
|
1402
|
+
const hasCursorCli = Boolean(cursorPath);
|
|
1436
1403
|
if (json) {
|
|
1437
|
-
return {
|
|
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(
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|