happy-stacks 0.1.2 → 0.2.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 +121 -83
- 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 +560 -112
- package/scripts/build.mjs +24 -4
- package/scripts/cli-link.mjs +3 -3
- package/scripts/completion.mjs +15 -8
- package/scripts/daemon.mjs +130 -20
- package/scripts/dev.mjs +201 -133
- package/scripts/doctor.mjs +26 -21
- package/scripts/edison.mjs +1828 -0
- package/scripts/happy.mjs +3 -7
- package/scripts/init.mjs +43 -20
- package/scripts/install.mjs +14 -8
- package/scripts/lint.mjs +145 -0
- package/scripts/menubar.mjs +81 -8
- package/scripts/migrate.mjs +25 -15
- package/scripts/mobile.mjs +13 -7
- package/scripts/run.mjs +114 -27
- package/scripts/self.mjs +3 -7
- package/scripts/server_flavor.mjs +3 -3
- package/scripts/service.mjs +15 -2
- package/scripts/setup.mjs +790 -0
- package/scripts/setup_pr.mjs +182 -0
- package/scripts/stack.mjs +1792 -254
- package/scripts/stop.mjs +6 -3
- package/scripts/tailscale.mjs +17 -2
- package/scripts/test.mjs +144 -0
- package/scripts/tui.mjs +556 -0
- package/scripts/typecheck.mjs +2 -2
- package/scripts/ui_gateway.mjs +2 -2
- package/scripts/uninstall.mjs +18 -10
- package/scripts/utils/auth_files.mjs +58 -0
- package/scripts/utils/auth_login_ux.mjs +76 -0
- package/scripts/utils/auth_sources.mjs +12 -0
- package/scripts/utils/browser.mjs +22 -0
- package/scripts/utils/canonical_home.mjs +20 -0
- package/scripts/utils/{cli_registry.mjs → cli/cli_registry.mjs} +48 -0
- package/scripts/utils/{wizard.mjs → cli/wizard.mjs} +1 -1
- package/scripts/utils/config.mjs +6 -2
- package/scripts/utils/dev_auth_key.mjs +169 -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/env.mjs +60 -11
- package/scripts/utils/env_file.mjs +36 -0
- package/scripts/utils/expo.mjs +4 -2
- package/scripts/utils/handy_master_secret.mjs +94 -0
- package/scripts/utils/happy_server_infra.mjs +100 -46
- package/scripts/utils/localhost_host.mjs +17 -0
- package/scripts/utils/ownership.mjs +135 -0
- package/scripts/utils/paths.mjs +5 -2
- package/scripts/utils/pm.mjs +121 -20
- package/scripts/utils/proc.mjs +29 -2
- package/scripts/utils/runtime.mjs +1 -3
- package/scripts/utils/sandbox.mjs +14 -0
- package/scripts/utils/server.mjs +24 -0
- package/scripts/utils/server_port.mjs +9 -0
- package/scripts/utils/server_urls.mjs +54 -0
- package/scripts/utils/stack_context.mjs +23 -0
- package/scripts/utils/stack_runtime_state.mjs +104 -0
- package/scripts/utils/stack_startup.mjs +208 -0
- package/scripts/utils/stack_stop.mjs +79 -30
- package/scripts/utils/stacks.mjs +38 -0
- package/scripts/utils/watch.mjs +63 -0
- package/scripts/utils/worktrees.mjs +57 -1
- package/scripts/where.mjs +14 -7
- package/scripts/worktrees.mjs +82 -8
- /package/scripts/utils/{args.mjs → cli/args.mjs} +0 -0
- /package/scripts/utils/{cli.mjs → cli/cli.mjs} +0 -0
- /package/scripts/utils/{smoke_help.mjs → cli/smoke_help.mjs} +0 -0
package/scripts/stop.mjs
CHANGED
|
@@ -2,15 +2,15 @@ import './utils/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';
|
|
5
|
+
import { parseArgs } from './utils/cli/args.mjs';
|
|
6
|
+
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
|
|
7
7
|
import { run, runCapture } from './utils/proc.mjs';
|
|
8
8
|
import { getRootDir, resolveStackEnvPath } from './utils/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,8 @@
|
|
|
1
1
|
import './utils/env.mjs';
|
|
2
|
-
import { parseArgs } from './utils/args.mjs';
|
|
2
|
+
import { parseArgs } from './utils/cli/args.mjs';
|
|
3
3
|
import { run, runCapture } from './utils/proc.mjs';
|
|
4
|
-
import { printResult, wantsHelp, wantsJson } from './utils/cli.mjs';
|
|
4
|
+
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
|
|
5
|
+
import { isSandboxed, sandboxAllowsGlobalSideEffects } from './utils/sandbox.mjs';
|
|
5
6
|
import { constants } from 'node:fs';
|
|
6
7
|
import { access } from 'node:fs/promises';
|
|
7
8
|
|
|
@@ -369,6 +370,13 @@ async function main() {
|
|
|
369
370
|
return;
|
|
370
371
|
}
|
|
371
372
|
case 'enable': {
|
|
373
|
+
if (isSandboxed() && !sandboxAllowsGlobalSideEffects()) {
|
|
374
|
+
throw new Error(
|
|
375
|
+
'[tailscale] enable is disabled in sandbox mode.\n' +
|
|
376
|
+
'Reason: Tailscale Serve is global machine state.\n' +
|
|
377
|
+
'If you really want this, set: HAPPY_STACKS_SANDBOX_ALLOW_GLOBAL=1'
|
|
378
|
+
);
|
|
379
|
+
}
|
|
372
380
|
const res = await tailscaleServeEnable({ internalServerUrl });
|
|
373
381
|
if (res?.enableUrl && !res?.httpsUrl) {
|
|
374
382
|
printResult({
|
|
@@ -387,6 +395,13 @@ async function main() {
|
|
|
387
395
|
}
|
|
388
396
|
case 'disable':
|
|
389
397
|
case 'reset': {
|
|
398
|
+
if (isSandboxed() && !sandboxAllowsGlobalSideEffects()) {
|
|
399
|
+
throw new Error(
|
|
400
|
+
'[tailscale] disable/reset is disabled in sandbox mode.\n' +
|
|
401
|
+
'Reason: Tailscale Serve is global machine state.\n' +
|
|
402
|
+
'If you really want this, set: HAPPY_STACKS_SANDBOX_ALLOW_GLOBAL=1'
|
|
403
|
+
);
|
|
404
|
+
}
|
|
390
405
|
await tailscaleServeReset();
|
|
391
406
|
printResult({ json, data: { ok: true }, text: '[local] tailscale serve reset' });
|
|
392
407
|
return;
|
package/scripts/test.mjs
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import './utils/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.mjs';
|
|
5
|
+
import { ensureDepsInstalled, requirePnpm } from './utils/pm.mjs';
|
|
6
|
+
import { pathExists } from './utils/fs.mjs';
|
|
7
|
+
import { run } from './utils/proc.mjs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { readFile } from 'node:fs/promises';
|
|
10
|
+
|
|
11
|
+
const DEFAULT_COMPONENTS = ['happy', 'happy-cli', 'happy-server-light', 'happy-server'];
|
|
12
|
+
|
|
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
|
+
function pickTestScript(scripts) {
|
|
33
|
+
if (!scripts) return null;
|
|
34
|
+
const candidates = [
|
|
35
|
+
'test',
|
|
36
|
+
'tst',
|
|
37
|
+
'test:ci',
|
|
38
|
+
'test:unit',
|
|
39
|
+
'check:test',
|
|
40
|
+
];
|
|
41
|
+
return candidates.find((k) => typeof scripts[k] === 'string' && scripts[k].trim()) ?? null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function main() {
|
|
45
|
+
const argv = process.argv.slice(2);
|
|
46
|
+
const { flags } = parseArgs(argv);
|
|
47
|
+
const json = wantsJson(argv, { flags });
|
|
48
|
+
|
|
49
|
+
if (wantsHelp(argv, { flags })) {
|
|
50
|
+
printResult({
|
|
51
|
+
json,
|
|
52
|
+
data: { components: DEFAULT_COMPONENTS, flags: ['--json'] },
|
|
53
|
+
text: [
|
|
54
|
+
'[test] usage:',
|
|
55
|
+
' happys test [component...] [--json]',
|
|
56
|
+
'',
|
|
57
|
+
'components:',
|
|
58
|
+
` ${DEFAULT_COMPONENTS.join(' | ')}`,
|
|
59
|
+
'',
|
|
60
|
+
'examples:',
|
|
61
|
+
' happys test',
|
|
62
|
+
' happys test happy happy-cli',
|
|
63
|
+
].join('\n'),
|
|
64
|
+
});
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const positionals = argv.filter((a) => !a.startsWith('--'));
|
|
69
|
+
const requested = positionals.length ? positionals : ['all'];
|
|
70
|
+
const wantAll = requested.includes('all');
|
|
71
|
+
const components = wantAll ? DEFAULT_COMPONENTS : requested;
|
|
72
|
+
|
|
73
|
+
const rootDir = getRootDir(import.meta.url);
|
|
74
|
+
|
|
75
|
+
const results = [];
|
|
76
|
+
for (const component of components) {
|
|
77
|
+
if (!DEFAULT_COMPONENTS.includes(component)) {
|
|
78
|
+
results.push({ component, ok: false, skipped: false, error: `unknown component (expected one of: ${DEFAULT_COMPONENTS.join(', ')})` });
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const dir = getComponentDir(rootDir, component);
|
|
83
|
+
if (!(await pathExists(dir))) {
|
|
84
|
+
results.push({ component, ok: false, skipped: false, dir, error: `missing component dir: ${dir}` });
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const scripts = await readScripts(dir);
|
|
89
|
+
if (!scripts) {
|
|
90
|
+
results.push({ component, ok: true, skipped: true, dir, reason: 'no package.json' });
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const script = pickTestScript(scripts);
|
|
95
|
+
if (!script) {
|
|
96
|
+
results.push({ component, ok: true, skipped: true, dir, reason: 'no test script found in package.json' });
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
await ensureDepsInstalled(dir, component);
|
|
101
|
+
const pm = await detectPackageManagerCmd(dir);
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
// eslint-disable-next-line no-console
|
|
105
|
+
console.log(`[test] ${component}: running ${pm.name} ${script}`);
|
|
106
|
+
await run(pm.cmd, pm.argsForScript(script), { cwd: dir, env: process.env });
|
|
107
|
+
results.push({ component, ok: true, skipped: false, dir, pm: pm.name, script });
|
|
108
|
+
} catch (e) {
|
|
109
|
+
results.push({ component, ok: false, skipped: false, dir, pm: pm.name, script, error: String(e?.message ?? e) });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const ok = results.every((r) => r.ok);
|
|
114
|
+
if (json) {
|
|
115
|
+
printResult({ json, data: { ok, results } });
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const lines = ['[test] results:'];
|
|
120
|
+
for (const r of results) {
|
|
121
|
+
if (r.ok && r.skipped) {
|
|
122
|
+
lines.push(`- ↪ ${r.component}: skipped (${r.reason})`);
|
|
123
|
+
} else if (r.ok) {
|
|
124
|
+
lines.push(`- ✅ ${r.component}: ok (${r.pm} ${r.script})`);
|
|
125
|
+
} else {
|
|
126
|
+
lines.push(`- ❌ ${r.component}: failed (${r.pm ?? 'unknown'} ${r.script ?? ''})`);
|
|
127
|
+
if (r.error) lines.push(` - ${r.error}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (!ok) {
|
|
131
|
+
lines.push('');
|
|
132
|
+
lines.push('[test] failed');
|
|
133
|
+
}
|
|
134
|
+
printResult({ json: false, text: lines.join('\n') });
|
|
135
|
+
if (!ok) {
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
main().catch((err) => {
|
|
141
|
+
console.error('[test] failed:', err);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
});
|
|
144
|
+
|