happy-stacks 0.1.2 → 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 +164 -89
- package/bin/happys.mjs +70 -10
- package/docs/edison.md +381 -0
- package/docs/happy-development.md +733 -0
- package/docs/menubar.md +54 -0
- package/docs/paths-and-env.md +141 -0
- package/docs/stacks.md +39 -0
- package/extras/swiftbar/auth-login.sh +5 -2
- package/extras/swiftbar/git-cache-refresh.sh +130 -0
- package/extras/swiftbar/happy-stacks.5s.sh +131 -81
- package/extras/swiftbar/happys-term.sh +15 -38
- package/extras/swiftbar/happys.sh +15 -32
- package/extras/swiftbar/install.sh +99 -13
- package/extras/swiftbar/lib/git.sh +309 -1
- package/extras/swiftbar/lib/icons.sh +2 -2
- package/extras/swiftbar/lib/render.sh +209 -80
- package/extras/swiftbar/lib/system.sh +27 -4
- package/extras/swiftbar/lib/utils.sh +311 -28
- package/extras/swiftbar/pnpm.sh +2 -1
- package/extras/swiftbar/set-interval.sh +10 -5
- package/extras/swiftbar/set-server-flavor.sh +11 -2
- package/extras/swiftbar/wt-pr.sh +9 -2
- package/package.json +2 -1
- package/scripts/auth.mjs +521 -226
- package/scripts/build.mjs +29 -10
- package/scripts/cli-link.mjs +6 -6
- package/scripts/completion.mjs +18 -11
- package/scripts/daemon.mjs +133 -31
- package/scripts/dev.mjs +196 -137
- package/scripts/doctor.mjs +44 -55
- package/scripts/edison.mjs +1853 -0
- package/scripts/happy.mjs +10 -25
- package/scripts/init.mjs +46 -31
- package/scripts/install.mjs +21 -15
- package/scripts/lint.mjs +124 -0
- package/scripts/menubar.mjs +76 -10
- package/scripts/migrate.mjs +35 -35
- package/scripts/mobile.mjs +24 -17
- package/scripts/run.mjs +122 -35
- package/scripts/self.mjs +13 -35
- package/scripts/server_flavor.mjs +7 -7
- package/scripts/service.mjs +31 -28
- package/scripts/setup.mjs +694 -0
- package/scripts/setup_pr.mjs +165 -0
- package/scripts/stack.mjs +1851 -363
- package/scripts/stop.mjs +9 -6
- package/scripts/tailscale.mjs +23 -11
- package/scripts/test.mjs +123 -0
- package/scripts/tui.mjs +526 -0
- package/scripts/typecheck.mjs +10 -31
- package/scripts/ui_gateway.mjs +3 -3
- package/scripts/uninstall.mjs +21 -13
- package/scripts/utils/auth/dev_key.mjs +163 -0
- package/scripts/utils/auth/files.mjs +56 -0
- package/scripts/utils/auth/handy_master_secret.mjs +68 -0
- package/scripts/utils/auth/login_ux.mjs +76 -0
- package/scripts/utils/auth/sources.mjs +12 -0
- package/scripts/utils/{cli_registry.mjs → cli/cli_registry.mjs} +48 -0
- package/scripts/utils/cli/flags.mjs +17 -0
- package/scripts/utils/cli/normalize.mjs +16 -0
- package/scripts/utils/{smoke_help.mjs → cli/smoke_help.mjs} +2 -2
- package/scripts/utils/{wizard.mjs → cli/wizard.mjs} +1 -1
- package/scripts/utils/crypto/tokens.mjs +14 -0
- package/scripts/utils/dev/daemon.mjs +104 -0
- package/scripts/utils/dev/expo_web.mjs +112 -0
- package/scripts/utils/dev/server.mjs +183 -0
- package/scripts/utils/{config.mjs → env/config.mjs} +8 -3
- package/scripts/utils/{dotenv.mjs → env/dotenv.mjs} +3 -0
- package/scripts/utils/{env.mjs → env/env.mjs} +64 -13
- package/scripts/utils/{env_file.mjs → env/env_file.mjs} +38 -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/sandbox.mjs +14 -0
- package/scripts/utils/env/values.mjs +13 -0
- package/scripts/utils/{expo.mjs → expo/expo.mjs} +7 -11
- 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} +60 -4
- package/scripts/utils/net/dns.mjs +10 -0
- package/scripts/utils/{ports.mjs → net/ports.mjs} +3 -5
- package/scripts/utils/paths/canonical_home.mjs +20 -0
- package/scripts/utils/paths/localhost_host.mjs +9 -0
- package/scripts/utils/{paths.mjs → paths/paths.mjs} +14 -8
- package/scripts/utils/{runtime.mjs → paths/runtime.mjs} +4 -4
- package/scripts/utils/proc/commands.mjs +34 -0
- package/scripts/utils/proc/ownership.mjs +135 -0
- package/scripts/utils/proc/package_scripts.mjs +31 -0
- package/scripts/utils/proc/pids.mjs +11 -0
- package/scripts/utils/proc/pm.mjs +317 -0
- package/scripts/utils/{proc.mjs → proc/proc.mjs} +30 -2
- package/scripts/utils/proc/watch.mjs +63 -0
- package/scripts/utils/{happy_server_infra.mjs → server/infra/happy_server_infra.mjs} +109 -94
- package/scripts/utils/server/port.mjs +68 -0
- package/scripts/utils/{server.mjs → server/server.mjs} +36 -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 +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/runtime_state.mjs +87 -0
- package/scripts/utils/stack/stacks.mjs +45 -0
- package/scripts/utils/stack/startup.mjs +208 -0
- package/scripts/utils/{stack_stop.mjs → stack/stop.mjs} +85 -42
- package/scripts/utils/ui/browser.mjs +22 -0
- package/scripts/utils/ui/text.mjs +16 -0
- package/scripts/where.mjs +17 -10
- package/scripts/worktrees.mjs +110 -64
- package/scripts/utils/pm.mjs +0 -303
- /package/scripts/utils/{args.mjs → cli/args.mjs} +0 -0
- /package/scripts/utils/{cli.mjs → cli/cli.mjs} +0 -0
- /package/scripts/utils/{fs.mjs → fs/fs.mjs} +0 -0
package/scripts/stop.mjs
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
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
|
-
import { parseArgs } from './utils/args.mjs';
|
|
6
|
-
import { printResult, wantsHelp, wantsJson } from './utils/cli.mjs';
|
|
7
|
-
import { run, runCapture } from './utils/proc.mjs';
|
|
8
|
-
import { getRootDir, resolveStackEnvPath } from './utils/paths.mjs';
|
|
5
|
+
import { parseArgs } from './utils/cli/args.mjs';
|
|
6
|
+
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.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 [
|
|
12
12
|
'[stop] usage:',
|
|
13
|
-
' happys stop [--except-stacks=main,exp1] [--yes] [--aggressive] [--no-docker] [--no-service] [--json]',
|
|
13
|
+
' happys stop [--except-stacks=main,exp1] [--yes] [--aggressive] [--sweep-owned] [--no-docker] [--no-service] [--json]',
|
|
14
14
|
'',
|
|
15
15
|
'Stops stacks and related local processes (server, daemon, Expo, managed infra) using stack-scoped commands.',
|
|
16
16
|
'',
|
|
@@ -18,6 +18,7 @@ function usage() {
|
|
|
18
18
|
' happys stop --except-stacks=main --yes',
|
|
19
19
|
' happys stop --yes --no-docker',
|
|
20
20
|
' happys stop --except-stacks=main --yes --aggressive',
|
|
21
|
+
' happys stop --except-stacks=main --yes --aggressive --sweep-owned',
|
|
21
22
|
].join('\n');
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -59,6 +60,7 @@ async function main() {
|
|
|
59
60
|
const exceptStacks = new Set(parseCsv(kv.get('--except-stacks')));
|
|
60
61
|
const yes = flags.has('--yes');
|
|
61
62
|
const aggressive = flags.has('--aggressive');
|
|
63
|
+
const sweepOwned = flags.has('--sweep-owned');
|
|
62
64
|
const noDocker = flags.has('--no-docker');
|
|
63
65
|
const noService = flags.has('--no-service');
|
|
64
66
|
|
|
@@ -108,6 +110,7 @@ async function main() {
|
|
|
108
110
|
'stop',
|
|
109
111
|
stackName,
|
|
110
112
|
...(aggressive ? ['--aggressive'] : []),
|
|
113
|
+
...(sweepOwned ? ['--sweep-owned'] : []),
|
|
111
114
|
...(noDocker ? ['--no-docker'] : []),
|
|
112
115
|
];
|
|
113
116
|
if (json) {
|
package/scripts/tailscale.mjs
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import './utils/env.mjs';
|
|
2
|
-
import { parseArgs } from './utils/args.mjs';
|
|
3
|
-
import { run, runCapture } from './utils/proc.mjs';
|
|
4
|
-
import { printResult, wantsHelp, wantsJson } from './utils/cli.mjs';
|
|
1
|
+
import './utils/env/env.mjs';
|
|
2
|
+
import { parseArgs } from './utils/cli/args.mjs';
|
|
3
|
+
import { run, runCapture } from './utils/proc/proc.mjs';
|
|
4
|
+
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.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';
|
|
5
8
|
import { constants } from 'node:fs';
|
|
6
9
|
import { access } from 'node:fs/promises';
|
|
7
10
|
|
|
@@ -20,11 +23,6 @@ import { access } from 'node:fs/promises';
|
|
|
20
23
|
* - url (print the first https:// URL from status output)
|
|
21
24
|
*/
|
|
22
25
|
|
|
23
|
-
function getInternalServerUrl() {
|
|
24
|
-
const port = process.env.HAPPY_LOCAL_SERVER_PORT?.trim() ? Number(process.env.HAPPY_LOCAL_SERVER_PORT) : 3005;
|
|
25
|
-
return `http://127.0.0.1:${port}`;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
26
|
function getServeConfig(internalServerUrl) {
|
|
29
27
|
const upstream = process.env.HAPPY_LOCAL_TAILSCALE_UPSTREAM?.trim()
|
|
30
28
|
? process.env.HAPPY_LOCAL_TAILSCALE_UPSTREAM.trim()
|
|
@@ -133,7 +131,7 @@ async function resolveTailscaleCmd() {
|
|
|
133
131
|
|
|
134
132
|
// Try PATH first (without executing `tailscale`, which can hang in some environments).
|
|
135
133
|
try {
|
|
136
|
-
const found =
|
|
134
|
+
const found = await resolveCommandPath('tailscale', { env: tailscaleEnv(), timeoutMs: tailscaleProbeTimeoutMs() });
|
|
137
135
|
if (found) {
|
|
138
136
|
return found;
|
|
139
137
|
}
|
|
@@ -341,7 +339,7 @@ async function main() {
|
|
|
341
339
|
return;
|
|
342
340
|
}
|
|
343
341
|
|
|
344
|
-
const internalServerUrl = getInternalServerUrl();
|
|
342
|
+
const internalServerUrl = getInternalServerUrl({ env: process.env, defaultPort: 3005 }).internalServerUrl;
|
|
345
343
|
if (flags.has('--upstream') || kv.get('--upstream')) {
|
|
346
344
|
process.env.HAPPY_LOCAL_TAILSCALE_UPSTREAM = kv.get('--upstream') ?? internalServerUrl;
|
|
347
345
|
}
|
|
@@ -369,6 +367,13 @@ async function main() {
|
|
|
369
367
|
return;
|
|
370
368
|
}
|
|
371
369
|
case 'enable': {
|
|
370
|
+
if (isSandboxed() && !sandboxAllowsGlobalSideEffects()) {
|
|
371
|
+
throw new Error(
|
|
372
|
+
'[tailscale] enable is disabled in sandbox mode.\n' +
|
|
373
|
+
'Reason: Tailscale Serve is global machine state.\n' +
|
|
374
|
+
'If you really want this, set: HAPPY_STACKS_SANDBOX_ALLOW_GLOBAL=1'
|
|
375
|
+
);
|
|
376
|
+
}
|
|
372
377
|
const res = await tailscaleServeEnable({ internalServerUrl });
|
|
373
378
|
if (res?.enableUrl && !res?.httpsUrl) {
|
|
374
379
|
printResult({
|
|
@@ -387,6 +392,13 @@ async function main() {
|
|
|
387
392
|
}
|
|
388
393
|
case 'disable':
|
|
389
394
|
case 'reset': {
|
|
395
|
+
if (isSandboxed() && !sandboxAllowsGlobalSideEffects()) {
|
|
396
|
+
throw new Error(
|
|
397
|
+
'[tailscale] disable/reset is disabled in sandbox mode.\n' +
|
|
398
|
+
'Reason: Tailscale Serve is global machine state.\n' +
|
|
399
|
+
'If you really want this, set: HAPPY_STACKS_SANDBOX_ALLOW_GLOBAL=1'
|
|
400
|
+
);
|
|
401
|
+
}
|
|
390
402
|
await tailscaleServeReset();
|
|
391
403
|
printResult({ json, data: { ok: true }, text: '[local] tailscale serve reset' });
|
|
392
404
|
return;
|
package/scripts/test.mjs
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import './utils/env/env.mjs';
|
|
2
|
+
import { parseArgs } from './utils/cli/args.mjs';
|
|
3
|
+
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
|
|
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';
|
|
9
|
+
|
|
10
|
+
const DEFAULT_COMPONENTS = ['happy', 'happy-cli', 'happy-server-light', 'happy-server'];
|
|
11
|
+
|
|
12
|
+
function pickTestScript(scripts) {
|
|
13
|
+
const candidates = [
|
|
14
|
+
'test',
|
|
15
|
+
'tst',
|
|
16
|
+
'test:ci',
|
|
17
|
+
'test:unit',
|
|
18
|
+
'check:test',
|
|
19
|
+
];
|
|
20
|
+
return pickFirstScript(scripts, candidates);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function main() {
|
|
24
|
+
const argv = process.argv.slice(2);
|
|
25
|
+
const { flags } = parseArgs(argv);
|
|
26
|
+
const json = wantsJson(argv, { flags });
|
|
27
|
+
|
|
28
|
+
if (wantsHelp(argv, { flags })) {
|
|
29
|
+
printResult({
|
|
30
|
+
json,
|
|
31
|
+
data: { components: DEFAULT_COMPONENTS, flags: ['--json'] },
|
|
32
|
+
text: [
|
|
33
|
+
'[test] usage:',
|
|
34
|
+
' happys test [component...] [--json]',
|
|
35
|
+
'',
|
|
36
|
+
'components:',
|
|
37
|
+
` ${DEFAULT_COMPONENTS.join(' | ')}`,
|
|
38
|
+
'',
|
|
39
|
+
'examples:',
|
|
40
|
+
' happys test',
|
|
41
|
+
' happys test happy happy-cli',
|
|
42
|
+
].join('\n'),
|
|
43
|
+
});
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const positionals = argv.filter((a) => !a.startsWith('--'));
|
|
48
|
+
const requested = positionals.length ? positionals : ['all'];
|
|
49
|
+
const wantAll = requested.includes('all');
|
|
50
|
+
const components = wantAll ? DEFAULT_COMPONENTS : requested;
|
|
51
|
+
|
|
52
|
+
const rootDir = getRootDir(import.meta.url);
|
|
53
|
+
|
|
54
|
+
const results = [];
|
|
55
|
+
for (const component of components) {
|
|
56
|
+
if (!DEFAULT_COMPONENTS.includes(component)) {
|
|
57
|
+
results.push({ component, ok: false, skipped: false, error: `unknown component (expected one of: ${DEFAULT_COMPONENTS.join(', ')})` });
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const dir = getComponentDir(rootDir, component);
|
|
62
|
+
if (!(await pathExists(dir))) {
|
|
63
|
+
results.push({ component, ok: false, skipped: false, dir, error: `missing component dir: ${dir}` });
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const scripts = await readPackageJsonScripts(dir);
|
|
68
|
+
if (!scripts) {
|
|
69
|
+
results.push({ component, ok: true, skipped: true, dir, reason: 'no package.json' });
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const script = pickTestScript(scripts);
|
|
74
|
+
if (!script) {
|
|
75
|
+
results.push({ component, ok: true, skipped: true, dir, reason: 'no test script found in package.json' });
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
await ensureDepsInstalled(dir, component);
|
|
80
|
+
const pm = await detectPackageManagerCmd(dir);
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
// eslint-disable-next-line no-console
|
|
84
|
+
console.log(`[test] ${component}: running ${pm.name} ${script}`);
|
|
85
|
+
await run(pm.cmd, pm.argsForScript(script), { cwd: dir, env: process.env });
|
|
86
|
+
results.push({ component, ok: true, skipped: false, dir, pm: pm.name, script });
|
|
87
|
+
} catch (e) {
|
|
88
|
+
results.push({ component, ok: false, skipped: false, dir, pm: pm.name, script, error: String(e?.message ?? e) });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const ok = results.every((r) => r.ok);
|
|
93
|
+
if (json) {
|
|
94
|
+
printResult({ json, data: { ok, results } });
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const lines = ['[test] results:'];
|
|
99
|
+
for (const r of results) {
|
|
100
|
+
if (r.ok && r.skipped) {
|
|
101
|
+
lines.push(`- ↪ ${r.component}: skipped (${r.reason})`);
|
|
102
|
+
} else if (r.ok) {
|
|
103
|
+
lines.push(`- ✅ ${r.component}: ok (${r.pm} ${r.script})`);
|
|
104
|
+
} else {
|
|
105
|
+
lines.push(`- ❌ ${r.component}: failed (${r.pm ?? 'unknown'} ${r.script ?? ''})`);
|
|
106
|
+
if (r.error) lines.push(` - ${r.error}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (!ok) {
|
|
110
|
+
lines.push('');
|
|
111
|
+
lines.push('[test] failed');
|
|
112
|
+
}
|
|
113
|
+
printResult({ json: false, text: lines.join('\n') });
|
|
114
|
+
if (!ok) {
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
main().catch((err) => {
|
|
120
|
+
console.error('[test] failed:', err);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
});
|
|
123
|
+
|