happy-stacks 0.0.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 +314 -0
- package/bin/happys.mjs +168 -0
- package/docs/menubar.md +186 -0
- package/docs/mobile-ios.md +134 -0
- package/docs/remote-access.md +43 -0
- package/docs/server-flavors.md +79 -0
- package/docs/stacks.md +218 -0
- package/docs/tauri.md +62 -0
- package/docs/worktrees-and-forks.md +395 -0
- package/extras/swiftbar/auth-login.sh +31 -0
- package/extras/swiftbar/happy-stacks.5s.sh +218 -0
- package/extras/swiftbar/icons/happy-green.png +0 -0
- package/extras/swiftbar/icons/happy-orange.png +0 -0
- package/extras/swiftbar/icons/happy-red.png +0 -0
- package/extras/swiftbar/icons/logo-white.png +0 -0
- package/extras/swiftbar/install.sh +191 -0
- package/extras/swiftbar/lib/git.sh +330 -0
- package/extras/swiftbar/lib/icons.sh +105 -0
- package/extras/swiftbar/lib/render.sh +774 -0
- package/extras/swiftbar/lib/system.sh +190 -0
- package/extras/swiftbar/lib/utils.sh +205 -0
- package/extras/swiftbar/pnpm-term.sh +125 -0
- package/extras/swiftbar/pnpm.sh +21 -0
- package/extras/swiftbar/set-interval.sh +62 -0
- package/extras/swiftbar/set-server-flavor.sh +57 -0
- package/extras/swiftbar/wt-pr.sh +95 -0
- package/package.json +58 -0
- package/scripts/auth.mjs +272 -0
- package/scripts/build.mjs +204 -0
- package/scripts/cli-link.mjs +58 -0
- package/scripts/completion.mjs +364 -0
- package/scripts/daemon.mjs +349 -0
- package/scripts/dev.mjs +181 -0
- package/scripts/doctor.mjs +342 -0
- package/scripts/happy.mjs +79 -0
- package/scripts/init.mjs +232 -0
- package/scripts/install.mjs +379 -0
- package/scripts/menubar.mjs +107 -0
- package/scripts/mobile.mjs +305 -0
- package/scripts/run.mjs +236 -0
- package/scripts/self.mjs +298 -0
- package/scripts/server_flavor.mjs +125 -0
- package/scripts/service.mjs +526 -0
- package/scripts/stack.mjs +815 -0
- package/scripts/tailscale.mjs +278 -0
- package/scripts/uninstall.mjs +190 -0
- package/scripts/utils/args.mjs +17 -0
- package/scripts/utils/cli.mjs +24 -0
- package/scripts/utils/cli_registry.mjs +262 -0
- package/scripts/utils/config.mjs +40 -0
- package/scripts/utils/dotenv.mjs +30 -0
- package/scripts/utils/env.mjs +138 -0
- package/scripts/utils/env_file.mjs +59 -0
- package/scripts/utils/env_local.mjs +25 -0
- package/scripts/utils/fs.mjs +11 -0
- package/scripts/utils/paths.mjs +184 -0
- package/scripts/utils/pm.mjs +294 -0
- package/scripts/utils/ports.mjs +66 -0
- package/scripts/utils/proc.mjs +66 -0
- package/scripts/utils/runtime.mjs +30 -0
- package/scripts/utils/server.mjs +41 -0
- package/scripts/utils/smoke_help.mjs +45 -0
- package/scripts/utils/validate.mjs +47 -0
- package/scripts/utils/wizard.mjs +69 -0
- package/scripts/utils/worktrees.mjs +78 -0
- package/scripts/where.mjs +105 -0
- package/scripts/worktrees.mjs +1721 -0
package/scripts/self.mjs
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import './utils/env.mjs';
|
|
2
|
+
|
|
3
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
|
|
8
|
+
import { parseArgs } from './utils/args.mjs';
|
|
9
|
+
import { pathExists } from './utils/fs.mjs';
|
|
10
|
+
import { run, runCapture } from './utils/proc.mjs';
|
|
11
|
+
import { getHappyStacksHomeDir, getRootDir } from './utils/paths.mjs';
|
|
12
|
+
import { printResult, wantsHelp, wantsJson } from './utils/cli.mjs';
|
|
13
|
+
import { getRuntimeDir } from './utils/runtime.mjs';
|
|
14
|
+
|
|
15
|
+
function expandHome(p) {
|
|
16
|
+
return p.replace(/^~(?=\/)/, homedir());
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function cachePaths() {
|
|
20
|
+
const home = getHappyStacksHomeDir();
|
|
21
|
+
return {
|
|
22
|
+
home,
|
|
23
|
+
cacheDir: join(home, 'cache'),
|
|
24
|
+
updateJson: join(home, 'cache', 'update.json'),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function readJsonSafe(path) {
|
|
29
|
+
try {
|
|
30
|
+
const raw = await readFile(path, 'utf-8');
|
|
31
|
+
return JSON.parse(raw);
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function writeJsonSafe(path, obj) {
|
|
38
|
+
try {
|
|
39
|
+
await mkdir(join(path, '..'), { recursive: true });
|
|
40
|
+
} catch {
|
|
41
|
+
// ignore
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
await writeFile(path, JSON.stringify(obj, null, 2) + '\n', 'utf-8');
|
|
45
|
+
} catch {
|
|
46
|
+
// ignore
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function readPkgVersion(pkgJsonPath) {
|
|
51
|
+
try {
|
|
52
|
+
const raw = await readFile(pkgJsonPath, 'utf-8');
|
|
53
|
+
const pkg = JSON.parse(raw);
|
|
54
|
+
const v = String(pkg.version ?? '').trim();
|
|
55
|
+
return v || null;
|
|
56
|
+
} catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function getRuntimeInstalledVersion() {
|
|
62
|
+
const runtimeDir = getRuntimeDir();
|
|
63
|
+
const pkgJson = join(runtimeDir, 'node_modules', 'happy-stacks', 'package.json');
|
|
64
|
+
return await readPkgVersion(pkgJson);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function getInvokerVersion({ rootDir }) {
|
|
68
|
+
return await readPkgVersion(join(rootDir, 'package.json'));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function fetchLatestVersion() {
|
|
72
|
+
// Prefer npm (available on most systems with Node).
|
|
73
|
+
// Keep it simple: `npm view happy-stacks version` prints a single version.
|
|
74
|
+
try {
|
|
75
|
+
const out = (await runCapture('npm', ['view', 'happy-stacks', 'version'])).trim();
|
|
76
|
+
return out || null;
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function compareVersions(a, b) {
|
|
83
|
+
// Very small semver-ish compare (supports x.y.z); falls back to string compare.
|
|
84
|
+
const pa = String(a ?? '').trim().split('.').map((n) => Number(n));
|
|
85
|
+
const pb = String(b ?? '').trim().split('.').map((n) => Number(n));
|
|
86
|
+
if (pa.length >= 2 && pb.length >= 2 && pa.every((n) => Number.isFinite(n)) && pb.every((n) => Number.isFinite(n))) {
|
|
87
|
+
const len = Math.max(pa.length, pb.length);
|
|
88
|
+
for (let i = 0; i < len; i++) {
|
|
89
|
+
const da = pa[i] ?? 0;
|
|
90
|
+
const db = pb[i] ?? 0;
|
|
91
|
+
if (da > db) return 1;
|
|
92
|
+
if (da < db) return -1;
|
|
93
|
+
}
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
return String(a).localeCompare(String(b));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function cmdStatus({ rootDir, argv }) {
|
|
100
|
+
const { flags } = parseArgs(argv);
|
|
101
|
+
const json = wantsJson(argv, { flags });
|
|
102
|
+
const doCheck = !flags.has('--no-check');
|
|
103
|
+
|
|
104
|
+
const { updateJson, cacheDir } = cachePaths();
|
|
105
|
+
const invokerVersion = await getInvokerVersion({ rootDir });
|
|
106
|
+
const runtimeDir = getRuntimeDir();
|
|
107
|
+
const runtimeVersion = await getRuntimeInstalledVersion();
|
|
108
|
+
|
|
109
|
+
const cached = await readJsonSafe(updateJson);
|
|
110
|
+
|
|
111
|
+
let latest = cached?.latest ?? null;
|
|
112
|
+
let checkedAt = cached?.checkedAt ?? null;
|
|
113
|
+
let updateAvailable = Boolean(cached?.updateAvailable);
|
|
114
|
+
|
|
115
|
+
if (doCheck) {
|
|
116
|
+
try {
|
|
117
|
+
latest = await fetchLatestVersion();
|
|
118
|
+
checkedAt = Date.now();
|
|
119
|
+
const current = runtimeVersion || invokerVersion;
|
|
120
|
+
updateAvailable = Boolean(current && latest && compareVersions(latest, current) > 0);
|
|
121
|
+
await mkdir(cacheDir, { recursive: true });
|
|
122
|
+
await writeJsonSafe(updateJson, {
|
|
123
|
+
checkedAt,
|
|
124
|
+
latest,
|
|
125
|
+
current: current || null,
|
|
126
|
+
runtimeVersion: runtimeVersion || null,
|
|
127
|
+
invokerVersion: invokerVersion || null,
|
|
128
|
+
updateAvailable,
|
|
129
|
+
notifiedAt: cached?.notifiedAt ?? null,
|
|
130
|
+
});
|
|
131
|
+
} catch {
|
|
132
|
+
// ignore network/npm failures; keep cached values
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
printResult({
|
|
137
|
+
json,
|
|
138
|
+
data: {
|
|
139
|
+
ok: true,
|
|
140
|
+
invoker: { version: invokerVersion, rootDir },
|
|
141
|
+
runtime: { dir: runtimeDir, installed: Boolean(runtimeVersion), version: runtimeVersion },
|
|
142
|
+
update: { cachedLatest: cached?.latest ?? null, latest, checkedAt, updateAvailable },
|
|
143
|
+
},
|
|
144
|
+
text: [
|
|
145
|
+
`[self] invoker: ${invokerVersion ?? 'unknown'} (${rootDir})`,
|
|
146
|
+
`[self] runtime: ${runtimeVersion ? runtimeVersion : 'not installed'} (${runtimeDir})`,
|
|
147
|
+
latest ? `[self] latest: ${latest}${updateAvailable ? ' (update available)' : ''}` : `[self] latest: unknown`,
|
|
148
|
+
checkedAt ? `[self] checked: ${new Date(checkedAt).toISOString()}` : null,
|
|
149
|
+
]
|
|
150
|
+
.filter(Boolean)
|
|
151
|
+
.join('\n'),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function cmdUpdate({ rootDir, argv }) {
|
|
156
|
+
const { flags, kv } = parseArgs(argv);
|
|
157
|
+
const json = wantsJson(argv, { flags });
|
|
158
|
+
|
|
159
|
+
const runtimeDir = getRuntimeDir();
|
|
160
|
+
const to = (kv.get('--to') ?? '').trim();
|
|
161
|
+
const spec = to ? `happy-stacks@${to}` : 'happy-stacks@latest';
|
|
162
|
+
|
|
163
|
+
// Ensure runtime dir exists.
|
|
164
|
+
await mkdir(runtimeDir, { recursive: true });
|
|
165
|
+
|
|
166
|
+
// Install/update runtime package.
|
|
167
|
+
try {
|
|
168
|
+
await run('npm', ['install', '--no-audit', '--no-fund', '--silent', '--prefix', runtimeDir, spec], { cwd: rootDir });
|
|
169
|
+
} catch (err) {
|
|
170
|
+
// Pre-publish dev fallback: allow updating runtime from the local checkout.
|
|
171
|
+
if (!to && existsSync(join(rootDir, 'package.json'))) {
|
|
172
|
+
try {
|
|
173
|
+
const raw = await readFile(join(rootDir, 'package.json'), 'utf-8');
|
|
174
|
+
const pkg = JSON.parse(raw);
|
|
175
|
+
if (pkg?.name === 'happy-stacks') {
|
|
176
|
+
await run('npm', ['install', '--no-audit', '--no-fund', '--silent', '--prefix', runtimeDir, rootDir], { cwd: rootDir });
|
|
177
|
+
} else {
|
|
178
|
+
throw err;
|
|
179
|
+
}
|
|
180
|
+
} catch {
|
|
181
|
+
throw err;
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
throw err;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Refresh cache best-effort.
|
|
189
|
+
try {
|
|
190
|
+
const latest = await fetchLatestVersion();
|
|
191
|
+
const runtimeVersion = await getRuntimeInstalledVersion();
|
|
192
|
+
const invokerVersion = await getInvokerVersion({ rootDir });
|
|
193
|
+
const current = runtimeVersion || invokerVersion;
|
|
194
|
+
const updateAvailable = Boolean(current && latest && compareVersions(latest, current) > 0);
|
|
195
|
+
const { updateJson, cacheDir } = cachePaths();
|
|
196
|
+
await mkdir(cacheDir, { recursive: true });
|
|
197
|
+
await writeJsonSafe(updateJson, {
|
|
198
|
+
checkedAt: Date.now(),
|
|
199
|
+
latest,
|
|
200
|
+
current: current || null,
|
|
201
|
+
runtimeVersion: runtimeVersion || null,
|
|
202
|
+
invokerVersion: invokerVersion || null,
|
|
203
|
+
updateAvailable,
|
|
204
|
+
notifiedAt: null,
|
|
205
|
+
});
|
|
206
|
+
} catch {
|
|
207
|
+
// ignore
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const runtimeVersionAfter = await getRuntimeInstalledVersion();
|
|
211
|
+
printResult({
|
|
212
|
+
json,
|
|
213
|
+
data: { ok: true, runtimeDir, version: runtimeVersionAfter ?? null, spec },
|
|
214
|
+
text: `[self] updated runtime in ${runtimeDir} (${runtimeVersionAfter ?? spec})`,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function cmdCheck({ rootDir, argv }) {
|
|
219
|
+
const { flags } = parseArgs(argv);
|
|
220
|
+
const json = wantsJson(argv, { flags });
|
|
221
|
+
const quiet = flags.has('--quiet');
|
|
222
|
+
|
|
223
|
+
const { updateJson, cacheDir } = cachePaths();
|
|
224
|
+
const runtimeVersion = await getRuntimeInstalledVersion();
|
|
225
|
+
const invokerVersion = await getInvokerVersion({ rootDir });
|
|
226
|
+
const current = runtimeVersion || invokerVersion;
|
|
227
|
+
|
|
228
|
+
let latest = null;
|
|
229
|
+
try {
|
|
230
|
+
latest = await fetchLatestVersion();
|
|
231
|
+
} catch {
|
|
232
|
+
latest = null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const updateAvailable = Boolean(current && latest && compareVersions(latest, current) > 0);
|
|
236
|
+
await mkdir(cacheDir, { recursive: true });
|
|
237
|
+
await writeJsonSafe(updateJson, {
|
|
238
|
+
checkedAt: Date.now(),
|
|
239
|
+
latest,
|
|
240
|
+
current: current || null,
|
|
241
|
+
runtimeVersion: runtimeVersion || null,
|
|
242
|
+
invokerVersion: invokerVersion || null,
|
|
243
|
+
updateAvailable,
|
|
244
|
+
notifiedAt: null,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
if (quiet) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
printResult({
|
|
251
|
+
json,
|
|
252
|
+
data: { ok: true, current: current || null, latest, updateAvailable },
|
|
253
|
+
text: latest ? (updateAvailable ? `[self] update available: ${current} -> ${latest}` : `[self] up to date (${current})`) : '[self] unable to check latest version',
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async function main() {
|
|
258
|
+
const rootDir = getRootDir(import.meta.url);
|
|
259
|
+
const argv = process.argv.slice(2);
|
|
260
|
+
|
|
261
|
+
const { flags } = parseArgs(argv);
|
|
262
|
+
const cmd = argv.find((a) => !a.startsWith('--')) ?? 'help';
|
|
263
|
+
|
|
264
|
+
if (wantsHelp(argv, { flags }) || cmd === 'help') {
|
|
265
|
+
const json = wantsJson(argv, { flags });
|
|
266
|
+
printResult({
|
|
267
|
+
json,
|
|
268
|
+
data: { commands: ['status', 'update', 'check'], flags: ['--no-check', '--to=<version>', '--quiet'] },
|
|
269
|
+
text: [
|
|
270
|
+
'[self] usage:',
|
|
271
|
+
' happys self status [--no-check] [--json]',
|
|
272
|
+
' happys self update [--to=<version>] [--json]',
|
|
273
|
+
' happys self check [--quiet] [--json]',
|
|
274
|
+
].join('\n'),
|
|
275
|
+
});
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (cmd === 'status') {
|
|
280
|
+
await cmdStatus({ rootDir, argv });
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if (cmd === 'update') {
|
|
284
|
+
await cmdUpdate({ rootDir, argv });
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (cmd === 'check') {
|
|
288
|
+
await cmdCheck({ rootDir, argv });
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
throw new Error(`[self] unknown command: ${cmd}`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
main().catch((err) => {
|
|
296
|
+
console.error('[self] failed:', err);
|
|
297
|
+
process.exit(1);
|
|
298
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import './utils/env.mjs';
|
|
2
|
+
import { parseArgs } from './utils/args.mjs';
|
|
3
|
+
import { getRootDir } from './utils/paths.mjs';
|
|
4
|
+
import { ensureEnvFileUpdated } from './utils/env_file.mjs';
|
|
5
|
+
import { resolveUserConfigEnvPath } from './utils/config.mjs';
|
|
6
|
+
import { isTty, promptSelect, withRl } from './utils/wizard.mjs';
|
|
7
|
+
import { printResult, wantsHelp, wantsJson } from './utils/cli.mjs';
|
|
8
|
+
|
|
9
|
+
const FLAVORS = [
|
|
10
|
+
{ label: 'happy-server-light (recommended default, serves UI)', value: 'happy-server-light' },
|
|
11
|
+
{ label: 'happy-server (full server, no UI serving)', value: 'happy-server' },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
function normalizeFlavor(raw) {
|
|
15
|
+
const v = (raw ?? '').trim().toLowerCase();
|
|
16
|
+
if (!v) return '';
|
|
17
|
+
if (v === 'light' || v === 'server-light' || v === 'happy-server-light') return 'happy-server-light';
|
|
18
|
+
if (v === 'server' || v === 'full' || v === 'happy-server') return 'happy-server';
|
|
19
|
+
return raw.trim();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function cmdUse({ rootDir, argv }) {
|
|
23
|
+
const { flags } = parseArgs(argv);
|
|
24
|
+
const positionals = argv.filter((a) => !a.startsWith('--'));
|
|
25
|
+
const flavorRaw = positionals[1] ?? '';
|
|
26
|
+
const flavor = normalizeFlavor(flavorRaw);
|
|
27
|
+
if (!flavor) {
|
|
28
|
+
throw new Error('[server-flavor] usage: happys srv use <happy-server-light|happy-server> [--json]');
|
|
29
|
+
}
|
|
30
|
+
if (!['happy-server-light', 'happy-server'].includes(flavor)) {
|
|
31
|
+
throw new Error(`[server-flavor] unknown flavor: ${flavor}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const envPath = resolveUserConfigEnvPath({ cliRootDir: rootDir });
|
|
35
|
+
await ensureEnvFileUpdated({
|
|
36
|
+
envPath,
|
|
37
|
+
updates: [
|
|
38
|
+
{ key: 'HAPPY_STACKS_SERVER_COMPONENT', value: flavor },
|
|
39
|
+
{ key: 'HAPPY_LOCAL_SERVER_COMPONENT', value: flavor }, // legacy alias
|
|
40
|
+
],
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const json = wantsJson(argv, { flags });
|
|
44
|
+
printResult({
|
|
45
|
+
json,
|
|
46
|
+
data: { ok: true, flavor },
|
|
47
|
+
text: `[server-flavor] set HAPPY_STACKS_SERVER_COMPONENT=${flavor} (saved to ${envPath})`,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function cmdUseInteractive({ rootDir, argv }) {
|
|
52
|
+
const { flags } = parseArgs(argv);
|
|
53
|
+
const json = wantsJson(argv, { flags });
|
|
54
|
+
|
|
55
|
+
await withRl(async (rl) => {
|
|
56
|
+
const flavor = await promptSelect(rl, { title: 'Select server flavor:', options: FLAVORS, defaultIndex: 0 });
|
|
57
|
+
const envPath = resolveUserConfigEnvPath({ cliRootDir: rootDir });
|
|
58
|
+
await ensureEnvFileUpdated({
|
|
59
|
+
envPath,
|
|
60
|
+
updates: [
|
|
61
|
+
{ key: 'HAPPY_STACKS_SERVER_COMPONENT', value: flavor },
|
|
62
|
+
{ key: 'HAPPY_LOCAL_SERVER_COMPONENT', value: flavor }, // legacy alias
|
|
63
|
+
],
|
|
64
|
+
});
|
|
65
|
+
printResult({
|
|
66
|
+
json,
|
|
67
|
+
data: { ok: true, flavor },
|
|
68
|
+
text: `[server-flavor] set HAPPY_STACKS_SERVER_COMPONENT=${flavor} (saved to ${envPath})`,
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function cmdStatus({ argv }) {
|
|
74
|
+
const { flags } = parseArgs(argv);
|
|
75
|
+
const json = wantsJson(argv, { flags });
|
|
76
|
+
const flavor = process.env.HAPPY_STACKS_SERVER_COMPONENT?.trim() || process.env.HAPPY_LOCAL_SERVER_COMPONENT?.trim() || 'happy-server-light';
|
|
77
|
+
printResult({ json, data: { flavor }, text: `[server-flavor] current: ${flavor}` });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function main() {
|
|
81
|
+
const rootDir = getRootDir(import.meta.url);
|
|
82
|
+
const argv = process.argv.slice(2);
|
|
83
|
+
const { flags } = parseArgs(argv);
|
|
84
|
+
const positionals = argv.filter((a) => !a.startsWith('--'));
|
|
85
|
+
const cmd = positionals[0] ?? 'help';
|
|
86
|
+
const json = wantsJson(argv, { flags });
|
|
87
|
+
|
|
88
|
+
if (wantsHelp(argv, { flags }) || cmd === 'help') {
|
|
89
|
+
printResult({
|
|
90
|
+
json,
|
|
91
|
+
data: { commands: ['status', 'use'] },
|
|
92
|
+
text: [
|
|
93
|
+
'[server-flavor] usage:',
|
|
94
|
+
' happys srv status [--json]',
|
|
95
|
+
' happys srv use <happy-server-light|happy-server> [--json]',
|
|
96
|
+
' happys srv use --interactive [--json]',
|
|
97
|
+
'',
|
|
98
|
+
'notes:',
|
|
99
|
+
' - `pnpm srv -- ...` still works inside a cloned repo (legacy).',
|
|
100
|
+
].join('\n'),
|
|
101
|
+
});
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (cmd === 'status') {
|
|
106
|
+
await cmdStatus({ argv });
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (cmd === 'use') {
|
|
110
|
+
const interactive = argv.includes('--interactive') || argv.includes('-i');
|
|
111
|
+
if (interactive && isTty()) {
|
|
112
|
+
await cmdUseInteractive({ rootDir, argv });
|
|
113
|
+
} else {
|
|
114
|
+
await cmdUse({ rootDir, argv });
|
|
115
|
+
}
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
throw new Error(`[server-flavor] unknown command: ${cmd}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
main().catch((err) => {
|
|
123
|
+
console.error('[server-flavor] failed:', err);
|
|
124
|
+
process.exit(1);
|
|
125
|
+
});
|