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/happy.mjs
CHANGED
|
@@ -1,27 +1,12 @@
|
|
|
1
|
-
import './utils/env.mjs';
|
|
1
|
+
import './utils/env/env.mjs';
|
|
2
2
|
import { execFileSync } from 'node:child_process';
|
|
3
3
|
import { existsSync } from 'node:fs';
|
|
4
|
-
import { homedir } from 'node:os';
|
|
5
4
|
import { join } from 'node:path';
|
|
6
|
-
import { parseArgs } from './utils/args.mjs';
|
|
7
|
-
import { printResult, wantsHelp, wantsJson } from './utils/cli.mjs';
|
|
8
|
-
import { getComponentDir,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
return p.replace(/^~(?=\/)/, homedir());
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function resolveCliHomeDir() {
|
|
15
|
-
const fromExplicit = (process.env.HAPPY_HOME_DIR ?? '').trim();
|
|
16
|
-
if (fromExplicit) {
|
|
17
|
-
return expandHome(fromExplicit);
|
|
18
|
-
}
|
|
19
|
-
const fromStacks = (process.env.HAPPY_STACKS_CLI_HOME_DIR ?? process.env.HAPPY_LOCAL_CLI_HOME_DIR ?? '').trim();
|
|
20
|
-
if (fromStacks) {
|
|
21
|
-
return expandHome(fromStacks);
|
|
22
|
-
}
|
|
23
|
-
return join(getDefaultAutostartPaths().baseDir, 'cli');
|
|
24
|
-
}
|
|
5
|
+
import { parseArgs } from './utils/cli/args.mjs';
|
|
6
|
+
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
|
|
7
|
+
import { getComponentDir, getRootDir, getStackName } from './utils/paths/paths.mjs';
|
|
8
|
+
import { resolveCliHomeDir } from './utils/stack/dirs.mjs';
|
|
9
|
+
import { getPublicServerUrlEnvOverride, resolveServerPortFromEnv } from './utils/server/urls.mjs';
|
|
25
10
|
|
|
26
11
|
async function main() {
|
|
27
12
|
const argv = process.argv.slice(2);
|
|
@@ -46,12 +31,12 @@ async function main() {
|
|
|
46
31
|
|
|
47
32
|
const rootDir = getRootDir(import.meta.url);
|
|
48
33
|
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
const serverPort =
|
|
34
|
+
const stackName =
|
|
35
|
+
(process.env.HAPPY_STACKS_STACK ?? process.env.HAPPY_LOCAL_STACK ?? '').toString().trim() || getStackName();
|
|
36
|
+
const serverPort = resolveServerPortFromEnv({ env: process.env, defaultPort: 3005 });
|
|
52
37
|
|
|
53
38
|
const internalServerUrl = `http://127.0.0.1:${serverPort}`;
|
|
54
|
-
const publicServerUrl = (
|
|
39
|
+
const { publicServerUrl } = getPublicServerUrlEnvOverride({ env: process.env, serverPort, stackName });
|
|
55
40
|
|
|
56
41
|
const cliHomeDir = resolveCliHomeDir();
|
|
57
42
|
|
package/scripts/init.mjs
CHANGED
|
@@ -4,21 +4,11 @@ import { homedir } from 'node:os';
|
|
|
4
4
|
import { dirname, join } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { spawnSync } from 'node:child_process';
|
|
7
|
-
import { ensureCanonicalHomeEnvUpdated, ensureHomeEnvUpdated } from './utils/config.mjs';
|
|
8
|
-
import { parseDotenv } from './utils/dotenv.mjs';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async function readJsonIfExists(path) {
|
|
15
|
-
try {
|
|
16
|
-
const raw = await readFile(path, 'utf-8');
|
|
17
|
-
return JSON.parse(raw);
|
|
18
|
-
} catch {
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
7
|
+
import { ensureCanonicalHomeEnvUpdated, ensureHomeEnvUpdated } from './utils/env/config.mjs';
|
|
8
|
+
import { parseDotenv } from './utils/env/dotenv.mjs';
|
|
9
|
+
import { expandHome } from './utils/paths/canonical_home.mjs';
|
|
10
|
+
import { readJsonIfExists } from './utils/fs/json.mjs';
|
|
11
|
+
import { isSandboxed, sandboxAllowsGlobalSideEffects } from './utils/env/sandbox.mjs';
|
|
22
12
|
|
|
23
13
|
function getCliRootDir() {
|
|
24
14
|
return dirname(dirname(fileURLToPath(import.meta.url)));
|
|
@@ -139,15 +129,15 @@ async function main() {
|
|
|
139
129
|
if (argv.includes('--help') || argv.includes('-h') || argv[0] === 'help') {
|
|
140
130
|
console.log([
|
|
141
131
|
'[init] usage:',
|
|
142
|
-
' happys init [--home-dir=/path] [--workspace-dir=/path] [--runtime-dir=/path] [--storage-dir=/path] [--cli-root-dir=/path] [--tailscale-bin=/path] [--tailscale-cmd-timeout-ms=MS] [--tailscale-enable-timeout-ms=MS] [--tailscale-enable-timeout-ms-auto=MS] [--tailscale-reset-timeout-ms=MS] [--install-path] [--no-runtime] [--force-runtime] [--no-bootstrap] [--] [bootstrap args...]',
|
|
132
|
+
' happys init [--canonical-home-dir=/path] [--home-dir=/path] [--workspace-dir=/path] [--runtime-dir=/path] [--storage-dir=/path] [--cli-root-dir=/path] [--tailscale-bin=/path] [--tailscale-cmd-timeout-ms=MS] [--tailscale-enable-timeout-ms=MS] [--tailscale-enable-timeout-ms-auto=MS] [--tailscale-reset-timeout-ms=MS] [--install-path] [--no-runtime] [--force-runtime] [--no-bootstrap] [--] [bootstrap args...]',
|
|
143
133
|
'',
|
|
144
134
|
'notes:',
|
|
145
|
-
' - writes
|
|
146
|
-
' - default workspace:
|
|
147
|
-
' - default runtime:
|
|
135
|
+
' - writes <canonicalHomeDir>/.env (stable pointer file; default: ~/.happy-stacks/.env)',
|
|
136
|
+
' - default workspace: <homeDir>/workspace',
|
|
137
|
+
' - default runtime: <homeDir>/runtime (recommended for services/SwiftBar)',
|
|
148
138
|
' - runtime install is skipped if the same version is already installed (use --force-runtime to reinstall)',
|
|
149
139
|
' - set HAPPY_STACKS_INIT_NO_RUNTIME=1 to persist skipping runtime installs on this machine',
|
|
150
|
-
' - optional: --install-path adds
|
|
140
|
+
' - optional: --install-path adds <homeDir>/bin to your shell PATH (idempotent)',
|
|
151
141
|
' - by default, runs `happys bootstrap --interactive` at the end (TTY only) IF components are not already present',
|
|
152
142
|
].join('\n'));
|
|
153
143
|
return;
|
|
@@ -159,7 +149,17 @@ async function main() {
|
|
|
159
149
|
//
|
|
160
150
|
// Other scripts load this pointer via `scripts/utils/env.mjs`, but `init.mjs` is often run before
|
|
161
151
|
// anything else (or directly from a repo checkout). So we load it here too.
|
|
162
|
-
const
|
|
152
|
+
const canonicalHomeDirRaw = parseArgValue(argv, 'canonical-home-dir');
|
|
153
|
+
const canonicalHomeDir = expandHome(firstNonEmpty(
|
|
154
|
+
canonicalHomeDirRaw,
|
|
155
|
+
process.env.HAPPY_STACKS_CANONICAL_HOME_DIR,
|
|
156
|
+
process.env.HAPPY_LOCAL_CANONICAL_HOME_DIR,
|
|
157
|
+
join(homedir(), '.happy-stacks'),
|
|
158
|
+
));
|
|
159
|
+
process.env.HAPPY_STACKS_CANONICAL_HOME_DIR = canonicalHomeDir;
|
|
160
|
+
process.env.HAPPY_LOCAL_CANONICAL_HOME_DIR = process.env.HAPPY_LOCAL_CANONICAL_HOME_DIR ?? canonicalHomeDir;
|
|
161
|
+
|
|
162
|
+
const canonicalEnvPath = join(canonicalHomeDir, '.env');
|
|
163
163
|
if (existsSync(canonicalEnvPath)) {
|
|
164
164
|
await loadEnvFile(canonicalEnvPath, { override: false });
|
|
165
165
|
await loadEnvFile(canonicalEnvPath, { override: true, overridePrefix: 'HAPPY_STACKS_' });
|
|
@@ -245,6 +245,7 @@ async function main() {
|
|
|
245
245
|
const nodePath = process.execPath;
|
|
246
246
|
|
|
247
247
|
await mkdir(homeDir, { recursive: true });
|
|
248
|
+
await mkdir(canonicalHomeDir, { recursive: true });
|
|
248
249
|
await mkdir(workspaceDir, { recursive: true });
|
|
249
250
|
await mkdir(join(workspaceDir, 'components'), { recursive: true });
|
|
250
251
|
await mkdir(runtimeDir, { recursive: true });
|
|
@@ -306,10 +307,10 @@ async function main() {
|
|
|
306
307
|
const shim = [
|
|
307
308
|
'#!/bin/bash',
|
|
308
309
|
'set -euo pipefail',
|
|
309
|
-
|
|
310
|
+
`CANONICAL_ENV="${canonicalEnvPath}"`,
|
|
310
311
|
'',
|
|
311
312
|
'# Best-effort: if env vars are not exported (common under launchd/SwiftBar),',
|
|
312
|
-
'# read the stable pointer file at
|
|
313
|
+
'# read the stable pointer file at CANONICAL_ENV to discover the real dirs.',
|
|
313
314
|
'if [[ -f "$CANONICAL_ENV" ]]; then',
|
|
314
315
|
' if [[ -z "${HAPPY_STACKS_HOME_DIR:-}" ]]; then',
|
|
315
316
|
' HAPPY_STACKS_HOME_DIR="$(grep -E \'^HAPPY_STACKS_HOME_DIR=\' "$CANONICAL_ENV" | head -n 1 | sed \'s/^HAPPY_STACKS_HOME_DIR=//\')" || true',
|
|
@@ -333,7 +334,7 @@ async function main() {
|
|
|
333
334
|
' fi',
|
|
334
335
|
'fi',
|
|
335
336
|
'',
|
|
336
|
-
|
|
337
|
+
`HOME_DIR="\${HAPPY_STACKS_HOME_DIR:-${canonicalHomeDir}}"`,
|
|
337
338
|
'ENV_FILE="$HOME_DIR/.env"',
|
|
338
339
|
'WORKDIR="${HAPPY_STACKS_WORKSPACE_DIR:-$HOME_DIR/workspace}"',
|
|
339
340
|
'if [[ -d "$WORKDIR" ]]; then',
|
|
@@ -370,22 +371,31 @@ async function main() {
|
|
|
370
371
|
await writeExecutable(happysShimPath, shim);
|
|
371
372
|
await writeExecutable(happyShimPath, `#!/bin/bash\nset -euo pipefail\nexec \"${happysShimPath}\" happy \"$@\"\n`);
|
|
372
373
|
|
|
374
|
+
let didInstallPath = false;
|
|
373
375
|
if (argv.includes('--install-path')) {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
console.log(
|
|
376
|
+
if (isSandboxed() && !sandboxAllowsGlobalSideEffects()) {
|
|
377
|
+
console.log('[init] sandbox mode: skipping --install-path (would modify your shell config)');
|
|
378
|
+
console.log('[init] tip: set HAPPY_STACKS_SANDBOX_ALLOW_GLOBAL=1 if you really want to test PATH modifications');
|
|
377
379
|
} else {
|
|
378
|
-
|
|
380
|
+
const res = await ensurePathInstalled({ homeDir });
|
|
381
|
+
didInstallPath = true;
|
|
382
|
+
if (res.updated) {
|
|
383
|
+
console.log(`[init] added ${homeDir}/bin to PATH via ${res.path}`);
|
|
384
|
+
} else {
|
|
385
|
+
console.log(`[init] PATH already configured in ${res.path}`);
|
|
386
|
+
}
|
|
379
387
|
}
|
|
380
388
|
}
|
|
381
389
|
|
|
390
|
+
const invokedBySetup = (process.env.HAPPY_STACKS_SETUP_CHILD ?? '').trim() === '1';
|
|
391
|
+
|
|
382
392
|
console.log('[init] complete');
|
|
383
393
|
console.log(`[init] home: ${homeDir}`);
|
|
384
394
|
console.log(`[init] workspace: ${workspaceDir}`);
|
|
385
395
|
console.log(`[init] shims: ${homeDir}/bin`);
|
|
386
396
|
console.log('');
|
|
387
397
|
|
|
388
|
-
if (!argv.includes('--install-path')) {
|
|
398
|
+
if (!argv.includes('--install-path') || !didInstallPath) {
|
|
389
399
|
console.log('[init] note: to use `happys` / `happy` from any terminal, add shims to PATH:');
|
|
390
400
|
console.log(` export PATH="${homeDir}/bin:$PATH"`);
|
|
391
401
|
console.log(' (or re-run: happys init --install-path)');
|
|
@@ -422,13 +432,18 @@ async function main() {
|
|
|
422
432
|
|
|
423
433
|
if (wantBootstrap && alreadyBootstrapped && !bootstrapExplicit) {
|
|
424
434
|
console.log('[init] bootstrap: already set up; skipping');
|
|
425
|
-
console.log('[init] tip:
|
|
435
|
+
console.log('[init] tip: for guided onboarding: happys setup');
|
|
426
436
|
console.log('');
|
|
427
437
|
}
|
|
428
438
|
|
|
439
|
+
// When `happys setup` drives init, avoid printing confusing “next steps”.
|
|
440
|
+
if (invokedBySetup) {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
|
|
429
444
|
console.log('[init] next steps:');
|
|
430
445
|
console.log(` export PATH=\"${homeDir}/bin:$PATH\"`);
|
|
431
|
-
console.log(' happys
|
|
446
|
+
console.log(' happys setup');
|
|
432
447
|
}
|
|
433
448
|
|
|
434
449
|
main().catch((err) => {
|
package/scripts/install.mjs
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
|
-
import './utils/env.mjs';
|
|
2
|
-
import { parseArgs } from './utils/args.mjs';
|
|
3
|
-
import { pathExists } from './utils/fs.mjs';
|
|
4
|
-
import { run } from './utils/proc.mjs';
|
|
5
|
-
import { getComponentDir, getRootDir } from './utils/paths.mjs';
|
|
6
|
-
import { getServerComponentName } from './utils/server.mjs';
|
|
7
|
-
import { ensureCliBuilt, ensureDepsInstalled, ensureHappyCliLocalNpmLinked } from './utils/pm.mjs';
|
|
1
|
+
import './utils/env/env.mjs';
|
|
2
|
+
import { parseArgs } from './utils/cli/args.mjs';
|
|
3
|
+
import { pathExists } from './utils/fs/fs.mjs';
|
|
4
|
+
import { run } from './utils/proc/proc.mjs';
|
|
5
|
+
import { getComponentDir, getRootDir } from './utils/paths/paths.mjs';
|
|
6
|
+
import { getServerComponentName } from './utils/server/server.mjs';
|
|
7
|
+
import { ensureCliBuilt, ensureDepsInstalled, ensureHappyCliLocalNpmLinked } from './utils/proc/pm.mjs';
|
|
8
8
|
import { dirname, join } from 'node:path';
|
|
9
9
|
import { mkdir } from 'node:fs/promises';
|
|
10
10
|
import { installService, uninstallService } from './service.mjs';
|
|
11
|
-
import { printResult, wantsHelp, wantsJson } from './utils/cli.mjs';
|
|
12
|
-
import { ensureEnvLocalUpdated } from './utils/env_local.mjs';
|
|
13
|
-
import { isTty, prompt, promptSelect, withRl } from './utils/wizard.mjs';
|
|
11
|
+
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
|
|
12
|
+
import { ensureEnvLocalUpdated } from './utils/env/env_local.mjs';
|
|
13
|
+
import { isTty, prompt, promptSelect, withRl } from './utils/cli/wizard.mjs';
|
|
14
|
+
import { isSandboxed, sandboxAllowsGlobalSideEffects } from './utils/env/sandbox.mjs';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Install/setup the local stack:
|
|
17
18
|
* - ensure components exist (optionally clone if missing)
|
|
18
19
|
* - install dependencies where needed
|
|
19
|
-
* - build happy-cli (optional) and install `happy`/`happys` shims under
|
|
20
|
+
* - build happy-cli (optional) and install `happy`/`happys` shims under `<homeDir>/bin`
|
|
20
21
|
* - build the web UI bundle (so `run` can serve it)
|
|
21
22
|
* - optional macOS autostart (LaunchAgent)
|
|
22
23
|
*/
|
|
@@ -152,7 +153,9 @@ async function interactiveWizard({ rootDir, defaults }) {
|
|
|
152
153
|
});
|
|
153
154
|
|
|
154
155
|
const enableAutostart = await promptSelect(rl, {
|
|
155
|
-
title:
|
|
156
|
+
title: isSandboxed()
|
|
157
|
+
? 'Enable macOS autostart (LaunchAgent)? (NOTE: sandbox mode; this is global OS state)'
|
|
158
|
+
: 'Enable macOS autostart (LaunchAgent)?',
|
|
156
159
|
options: [
|
|
157
160
|
{ label: 'no (default)', value: false },
|
|
158
161
|
{ label: 'yes', value: true },
|
|
@@ -211,6 +214,8 @@ async function main() {
|
|
|
211
214
|
const rootDir = getRootDir(import.meta.url);
|
|
212
215
|
|
|
213
216
|
const interactive = flags.has('--interactive') && isTty();
|
|
217
|
+
const allowGlobal = sandboxAllowsGlobalSideEffects();
|
|
218
|
+
const sandboxed = isSandboxed();
|
|
214
219
|
|
|
215
220
|
// Defaults for wizard.
|
|
216
221
|
const defaultRepoSource = resolveRepoSource({ flags });
|
|
@@ -220,7 +225,7 @@ async function main() {
|
|
|
220
225
|
upstreamOwner: 'slopus',
|
|
221
226
|
serverComponentName: getServerComponentName({ kv }),
|
|
222
227
|
allowClone: !flags.has('--no-clone') && ((process.env.HAPPY_LOCAL_CLONE_MISSING ?? '1') !== '0' || flags.has('--clone')),
|
|
223
|
-
enableAutostart: flags.has('--autostart') || (process.env.HAPPY_LOCAL_AUTOSTART ?? '0') === '1',
|
|
228
|
+
enableAutostart: (!sandboxed || allowGlobal) && (flags.has('--autostart') || (process.env.HAPPY_LOCAL_AUTOSTART ?? '0') === '1'),
|
|
224
229
|
buildTauri: flags.has('--tauri') && !flags.has('--no-tauri'),
|
|
225
230
|
};
|
|
226
231
|
|
|
@@ -260,7 +265,8 @@ async function main() {
|
|
|
260
265
|
const cloneMissingDefault = (process.env.HAPPY_LOCAL_CLONE_MISSING ?? '1') !== '0';
|
|
261
266
|
const allowClone =
|
|
262
267
|
wizard?.allowClone ?? (!flags.has('--no-clone') && (flags.has('--clone') || cloneMissingDefault));
|
|
263
|
-
const
|
|
268
|
+
const enableAutostartRaw = wizard?.enableAutostart ?? (flags.has('--autostart') || (process.env.HAPPY_LOCAL_AUTOSTART ?? '0') === '1');
|
|
269
|
+
const enableAutostart = sandboxed && !allowGlobal ? false : enableAutostartRaw;
|
|
264
270
|
const disableAutostart = flags.has('--no-autostart');
|
|
265
271
|
|
|
266
272
|
const serverComponentName = (wizard?.serverComponentName ?? getServerComponentName({ kv })).trim();
|
|
@@ -366,7 +372,7 @@ async function main() {
|
|
|
366
372
|
serverComponentName,
|
|
367
373
|
dirs: { serverLightDir, serverFullDir, cliDir: cliDirFinal, uiDir: uiDirFinal },
|
|
368
374
|
cloned: allowClone,
|
|
369
|
-
autostart: enableAutostart ? 'enabled' : disableAutostart ? 'disabled' : 'unchanged',
|
|
375
|
+
autostart: enableAutostart ? 'enabled' : sandboxed && enableAutostartRaw && !allowGlobal ? 'skipped (sandbox)' : disableAutostart ? 'disabled' : 'unchanged',
|
|
370
376
|
interactive: Boolean(wizard),
|
|
371
377
|
},
|
|
372
378
|
text: '[local] setup complete',
|
package/scripts/lint.mjs
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
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 pickLintScript(scripts) {
|
|
13
|
+
const candidates = [
|
|
14
|
+
'lint',
|
|
15
|
+
'lint:ci',
|
|
16
|
+
'check',
|
|
17
|
+
'check:lint',
|
|
18
|
+
'eslint',
|
|
19
|
+
'eslint:check',
|
|
20
|
+
];
|
|
21
|
+
return pickFirstScript(scripts, candidates);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function main() {
|
|
25
|
+
const argv = process.argv.slice(2);
|
|
26
|
+
const { flags } = parseArgs(argv);
|
|
27
|
+
const json = wantsJson(argv, { flags });
|
|
28
|
+
|
|
29
|
+
if (wantsHelp(argv, { flags })) {
|
|
30
|
+
printResult({
|
|
31
|
+
json,
|
|
32
|
+
data: { components: DEFAULT_COMPONENTS, flags: ['--json'] },
|
|
33
|
+
text: [
|
|
34
|
+
'[lint] usage:',
|
|
35
|
+
' happys lint [component...] [--json]',
|
|
36
|
+
'',
|
|
37
|
+
'components:',
|
|
38
|
+
` ${DEFAULT_COMPONENTS.join(' | ')}`,
|
|
39
|
+
'',
|
|
40
|
+
'examples:',
|
|
41
|
+
' happys lint',
|
|
42
|
+
' happys lint happy happy-cli',
|
|
43
|
+
].join('\n'),
|
|
44
|
+
});
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const positionals = argv.filter((a) => !a.startsWith('--'));
|
|
49
|
+
const requested = positionals.length ? positionals : ['all'];
|
|
50
|
+
const wantAll = requested.includes('all');
|
|
51
|
+
const components = wantAll ? DEFAULT_COMPONENTS : requested;
|
|
52
|
+
|
|
53
|
+
const rootDir = getRootDir(import.meta.url);
|
|
54
|
+
|
|
55
|
+
const results = [];
|
|
56
|
+
for (const component of components) {
|
|
57
|
+
if (!DEFAULT_COMPONENTS.includes(component)) {
|
|
58
|
+
results.push({ component, ok: false, skipped: false, error: `unknown component (expected one of: ${DEFAULT_COMPONENTS.join(', ')})` });
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const dir = getComponentDir(rootDir, component);
|
|
63
|
+
if (!(await pathExists(dir))) {
|
|
64
|
+
results.push({ component, ok: false, skipped: false, dir, error: `missing component dir: ${dir}` });
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const scripts = await readPackageJsonScripts(dir);
|
|
69
|
+
if (!scripts) {
|
|
70
|
+
results.push({ component, ok: true, skipped: true, dir, reason: 'no package.json' });
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const script = pickLintScript(scripts);
|
|
75
|
+
if (!script) {
|
|
76
|
+
results.push({ component, ok: true, skipped: true, dir, reason: 'no lint script found in package.json' });
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
await ensureDepsInstalled(dir, component);
|
|
81
|
+
const pm = await detectPackageManagerCmd(dir);
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
// eslint-disable-next-line no-console
|
|
85
|
+
console.log(`[lint] ${component}: running ${pm.name} ${script}`);
|
|
86
|
+
await run(pm.cmd, pm.argsForScript(script), { cwd: dir, env: process.env });
|
|
87
|
+
results.push({ component, ok: true, skipped: false, dir, pm: pm.name, script });
|
|
88
|
+
} catch (e) {
|
|
89
|
+
results.push({ component, ok: false, skipped: false, dir, pm: pm.name, script, error: String(e?.message ?? e) });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const ok = results.every((r) => r.ok);
|
|
94
|
+
if (json) {
|
|
95
|
+
printResult({ json, data: { ok, results } });
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const lines = ['[lint] results:'];
|
|
100
|
+
for (const r of results) {
|
|
101
|
+
if (r.ok && r.skipped) {
|
|
102
|
+
lines.push(`- ↪ ${r.component}: skipped (${r.reason})`);
|
|
103
|
+
} else if (r.ok) {
|
|
104
|
+
lines.push(`- ✅ ${r.component}: ok (${r.pm} ${r.script})`);
|
|
105
|
+
} else {
|
|
106
|
+
lines.push(`- ❌ ${r.component}: failed (${r.pm ?? 'unknown'} ${r.script ?? ''})`);
|
|
107
|
+
if (r.error) lines.push(` - ${r.error}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (!ok) {
|
|
111
|
+
lines.push('');
|
|
112
|
+
lines.push('[lint] failed');
|
|
113
|
+
}
|
|
114
|
+
printResult({ json: false, text: lines.join('\n') });
|
|
115
|
+
if (!ok) {
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
main().catch((err) => {
|
|
121
|
+
console.error('[lint] failed:', err);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
});
|
|
124
|
+
|
package/scripts/menubar.mjs
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import './utils/env.mjs';
|
|
1
|
+
import './utils/env/env.mjs';
|
|
2
2
|
import { cp, mkdir } from 'node:fs/promises';
|
|
3
3
|
import { existsSync } from 'node:fs';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { spawnSync } from 'node:child_process';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { createHash } from 'node:crypto';
|
|
7
|
+
import { getHappyStacksHomeDir, getRootDir } from './utils/paths/paths.mjs';
|
|
8
|
+
import { parseArgs } from './utils/cli/args.mjs';
|
|
9
|
+
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
|
|
10
|
+
import { ensureEnvLocalUpdated } from './utils/env/env_local.mjs';
|
|
11
|
+
import { isSandboxed, sandboxAllowsGlobalSideEffects } from './utils/env/sandbox.mjs';
|
|
12
|
+
import { normalizeProfile } from './utils/cli/normalize.mjs';
|
|
9
13
|
|
|
10
14
|
async function ensureSwiftbarAssets({ cliRootDir }) {
|
|
11
15
|
const homeDir = getHappyStacksHomeDir();
|
|
@@ -34,9 +38,20 @@ function openSwiftbarPluginsDir() {
|
|
|
34
38
|
}
|
|
35
39
|
}
|
|
36
40
|
|
|
37
|
-
function
|
|
41
|
+
function sandboxPluginBasename() {
|
|
42
|
+
const sandboxDir = (process.env.HAPPY_STACKS_SANDBOX_DIR ?? '').trim();
|
|
43
|
+
if (!sandboxDir) return '';
|
|
44
|
+
const hash = createHash('sha256').update(sandboxDir).digest('hex').slice(0, 10);
|
|
45
|
+
return `happy-stacks.sandbox-${hash}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function removeSwiftbarPlugins({ patterns }) {
|
|
49
|
+
const pats = (patterns ?? []).filter(Boolean);
|
|
50
|
+
const args = pats.length ? pats.map((p) => `"${p}"`).join(' ') : '"happy-stacks.*.sh" "happy-local.*.sh"';
|
|
38
51
|
const s =
|
|
39
|
-
|
|
52
|
+
`DIR="$(defaults read com.ameba.SwiftBar PluginDirectory 2>/dev/null)"; ` +
|
|
53
|
+
`if [[ -z "$DIR" ]]; then DIR="$HOME/Library/Application Support/SwiftBar/Plugins"; fi; ` +
|
|
54
|
+
`if [[ -d "$DIR" ]]; then rm -f "$DIR"/${args} 2>/dev/null || true; echo "$DIR"; else echo ""; fi`;
|
|
40
55
|
const res = spawnSync('bash', ['-lc', s], { encoding: 'utf-8' });
|
|
41
56
|
if (res.status !== 0) {
|
|
42
57
|
return null;
|
|
@@ -55,16 +70,19 @@ async function main() {
|
|
|
55
70
|
if (wantsHelp(argv, { flags }) || cmd === 'help') {
|
|
56
71
|
printResult({
|
|
57
72
|
json,
|
|
58
|
-
data: { commands: ['install', 'uninstall', 'open'] },
|
|
73
|
+
data: { commands: ['install', 'uninstall', 'open', 'mode', 'status'] },
|
|
59
74
|
text: [
|
|
60
75
|
'[menubar] usage:',
|
|
61
76
|
' happys menubar install [--json]',
|
|
62
77
|
' happys menubar uninstall [--json]',
|
|
63
78
|
' happys menubar open [--json]',
|
|
79
|
+
' happys menubar mode <selfhost|dev> [--json]',
|
|
80
|
+
' happys menubar status [--json]',
|
|
64
81
|
'',
|
|
65
82
|
'notes:',
|
|
66
83
|
' - installs SwiftBar plugin into the active SwiftBar plugin folder',
|
|
67
|
-
' - keeps plugin source under
|
|
84
|
+
' - keeps plugin source under <homeDir>/extras/swiftbar for stability',
|
|
85
|
+
' - sandbox mode: install/uninstall are disabled by default (set HAPPY_STACKS_SANDBOX_ALLOW_GLOBAL=1 to override)',
|
|
68
86
|
].join('\n'),
|
|
69
87
|
});
|
|
70
88
|
return;
|
|
@@ -82,15 +100,63 @@ async function main() {
|
|
|
82
100
|
}
|
|
83
101
|
|
|
84
102
|
if (cmd === 'menubar:uninstall' || cmd === 'uninstall') {
|
|
85
|
-
|
|
103
|
+
if (isSandboxed() && !sandboxAllowsGlobalSideEffects()) {
|
|
104
|
+
printResult({ json, data: { ok: true, skipped: 'sandbox' }, text: '[menubar] uninstall skipped (sandbox mode)' });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const patterns = isSandboxed()
|
|
108
|
+
? [`${sandboxPluginBasename()}.*.sh`]
|
|
109
|
+
: ['happy-stacks.*.sh', 'happy-local.*.sh'];
|
|
110
|
+
const dir = removeSwiftbarPlugins({ patterns });
|
|
86
111
|
printResult({ json, data: { ok: true, pluginsDir: dir }, text: dir ? `[menubar] removed plugins from ${dir}` : '[menubar] no plugins dir found' });
|
|
87
112
|
return;
|
|
88
113
|
}
|
|
89
114
|
|
|
115
|
+
if (cmd === 'status') {
|
|
116
|
+
const mode = (process.env.HAPPY_STACKS_MENUBAR_MODE ?? process.env.HAPPY_LOCAL_MENUBAR_MODE ?? 'dev').trim() || 'dev';
|
|
117
|
+
printResult({ json, data: { ok: true, mode }, text: `[menubar] mode: ${mode}` });
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (cmd === 'mode') {
|
|
122
|
+
const positionals = argv.filter((a) => !a.startsWith('--'));
|
|
123
|
+
const raw = positionals[1] ?? '';
|
|
124
|
+
const mode = normalizeProfile(raw);
|
|
125
|
+
if (!mode) {
|
|
126
|
+
throw new Error('[menubar] usage: happys menubar mode <selfhost|dev> [--json]');
|
|
127
|
+
}
|
|
128
|
+
await ensureEnvLocalUpdated({
|
|
129
|
+
rootDir: cliRootDir,
|
|
130
|
+
updates: [
|
|
131
|
+
{ key: 'HAPPY_STACKS_MENUBAR_MODE', value: mode },
|
|
132
|
+
{ key: 'HAPPY_LOCAL_MENUBAR_MODE', value: mode },
|
|
133
|
+
],
|
|
134
|
+
});
|
|
135
|
+
printResult({ json, data: { ok: true, mode }, text: `[menubar] mode set: ${mode}` });
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
90
139
|
if (cmd === 'menubar:install' || cmd === 'install') {
|
|
140
|
+
if (isSandboxed() && !sandboxAllowsGlobalSideEffects()) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
'[menubar] install is disabled in sandbox mode.\n' +
|
|
143
|
+
'Reason: SwiftBar plugin installation writes to a global user folder.\n' +
|
|
144
|
+
'If you really want this, set: HAPPY_STACKS_SANDBOX_ALLOW_GLOBAL=1'
|
|
145
|
+
);
|
|
146
|
+
}
|
|
91
147
|
const { destDir } = await ensureSwiftbarAssets({ cliRootDir });
|
|
92
148
|
const installer = join(destDir, 'install.sh');
|
|
93
|
-
const
|
|
149
|
+
const env = {
|
|
150
|
+
...process.env,
|
|
151
|
+
HAPPY_STACKS_HOME_DIR: getHappyStacksHomeDir(),
|
|
152
|
+
...(isSandboxed()
|
|
153
|
+
? {
|
|
154
|
+
HAPPY_STACKS_SWIFTBAR_PLUGIN_BASENAME: sandboxPluginBasename(),
|
|
155
|
+
HAPPY_STACKS_SWIFTBAR_PLUGIN_WRAPPER: '1',
|
|
156
|
+
}
|
|
157
|
+
: {}),
|
|
158
|
+
};
|
|
159
|
+
const res = spawnSync('bash', [installer, '--force'], { stdio: 'inherit', env });
|
|
94
160
|
if (res.status !== 0) {
|
|
95
161
|
process.exit(res.status ?? 1);
|
|
96
162
|
}
|
package/scripts/migrate.mjs
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
import './utils/env.mjs';
|
|
1
|
+
import './utils/env/env.mjs';
|
|
2
2
|
import { copyFile, mkdir, readFile } from 'node:fs/promises';
|
|
3
3
|
import { basename, join } from 'node:path';
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
5
5
|
|
|
6
|
-
import { parseArgs } from './utils/args.mjs';
|
|
7
|
-
import { printResult, wantsHelp, wantsJson } from './utils/cli.mjs';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { resolveStackEnvPath } from './utils/paths.mjs';
|
|
11
|
-
import { ensureDepsInstalled } from './utils/pm.mjs';
|
|
12
|
-
import { ensureHappyServerManagedInfra, applyHappyServerMigrations } from './utils/happy_server_infra.mjs';
|
|
13
|
-
import { runCapture } from './utils/proc.mjs';
|
|
6
|
+
import { parseArgs } from './utils/cli/args.mjs';
|
|
7
|
+
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
|
|
8
|
+
import { ensureEnvFileUpdated } from './utils/env/env_file.mjs';
|
|
9
|
+
import { readEnvObjectFromFile } from './utils/env/read.mjs';
|
|
10
|
+
import { resolveStackEnvPath } from './utils/paths/paths.mjs';
|
|
11
|
+
import { ensureDepsInstalled } from './utils/proc/pm.mjs';
|
|
12
|
+
import { ensureHappyServerManagedInfra, applyHappyServerMigrations } from './utils/server/infra/happy_server_infra.mjs';
|
|
13
|
+
import { runCapture } from './utils/proc/proc.mjs';
|
|
14
|
+
import { pickNextFreeTcpPort } from './utils/net/ports.mjs';
|
|
15
|
+
import { getEnvValue } from './utils/env/values.mjs';
|
|
14
16
|
|
|
15
17
|
function usage() {
|
|
16
18
|
return [
|
|
@@ -24,18 +26,7 @@ function usage() {
|
|
|
24
26
|
].join('\n');
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
try {
|
|
29
|
-
const raw = await readFile(envPath, 'utf-8');
|
|
30
|
-
return Object.fromEntries(parseDotenv(raw).entries());
|
|
31
|
-
} catch {
|
|
32
|
-
return {};
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function getEnvValue(env, key) {
|
|
37
|
-
return (env?.[key] ?? '').toString().trim();
|
|
38
|
-
}
|
|
29
|
+
const readEnvObject = readEnvObjectFromFile;
|
|
39
30
|
|
|
40
31
|
function parseFileDatabaseUrl(url) {
|
|
41
32
|
const raw = String(url ?? '').trim();
|
|
@@ -97,9 +88,15 @@ async function migrateLightToServer({ rootDir, fromStack, toStack, includeFiles,
|
|
|
97
88
|
}
|
|
98
89
|
|
|
99
90
|
const toPortRaw = getEnvValue(toEnv, 'HAPPY_STACKS_SERVER_PORT') || getEnvValue(toEnv, 'HAPPY_LOCAL_SERVER_PORT');
|
|
100
|
-
|
|
91
|
+
let toPort = toPortRaw ? Number(toPortRaw) : NaN;
|
|
92
|
+
const toEphemeral = !toPortRaw;
|
|
101
93
|
if (!Number.isFinite(toPort) || toPort <= 0) {
|
|
102
|
-
|
|
94
|
+
// Ephemeral-port stacks don't pin ports in env. Pick a free port for this one-off migration run.
|
|
95
|
+
toPort = await pickNextFreeTcpPort(3005);
|
|
96
|
+
if (!json) {
|
|
97
|
+
// eslint-disable-next-line no-console
|
|
98
|
+
console.log(`[migrate] to-stack has no pinned port; using ephemeral port ${toPort} for this migration run`);
|
|
99
|
+
}
|
|
103
100
|
}
|
|
104
101
|
|
|
105
102
|
// Ensure target secret is the same as source so auth tokens remain valid after migration.
|
|
@@ -111,17 +108,6 @@ async function migrateLightToServer({ rootDir, fromStack, toStack, includeFiles,
|
|
|
111
108
|
updates: [{ key: 'HAPPY_STACKS_HANDY_MASTER_SECRET_FILE', value: targetSecretPath }],
|
|
112
109
|
});
|
|
113
110
|
|
|
114
|
-
// Bring up infra and ensure env vars are present.
|
|
115
|
-
const infra = await ensureHappyServerManagedInfra({
|
|
116
|
-
stackName: toStack,
|
|
117
|
-
baseDir: to.baseDir,
|
|
118
|
-
serverPort: toPort,
|
|
119
|
-
publicServerUrl: `http://127.0.0.1:${toPort}`,
|
|
120
|
-
envPath: to.envPath,
|
|
121
|
-
env: process.env,
|
|
122
|
-
});
|
|
123
|
-
await applyHappyServerMigrations({ serverDir: fullDir, env: { ...process.env, ...infra.env } });
|
|
124
|
-
|
|
125
111
|
// Resolve component dirs (prefer stack-pinned dirs).
|
|
126
112
|
const lightDir = getEnvValue(fromEnv, 'HAPPY_STACKS_COMPONENT_DIR_HAPPY_SERVER_LIGHT') || getEnvValue(fromEnv, 'HAPPY_LOCAL_COMPONENT_DIR_HAPPY_SERVER_LIGHT');
|
|
127
113
|
const fullDir = getEnvValue(toEnv, 'HAPPY_STACKS_COMPONENT_DIR_HAPPY_SERVER') || getEnvValue(toEnv, 'HAPPY_LOCAL_COMPONENT_DIR_HAPPY_SERVER');
|
|
@@ -132,6 +118,20 @@ async function migrateLightToServer({ rootDir, fromStack, toStack, includeFiles,
|
|
|
132
118
|
await ensureDepsInstalled(lightDir, 'happy-server-light');
|
|
133
119
|
await ensureDepsInstalled(fullDir, 'happy-server');
|
|
134
120
|
|
|
121
|
+
// Bring up infra and ensure env vars are present.
|
|
122
|
+
const infra = await ensureHappyServerManagedInfra({
|
|
123
|
+
stackName: toStack,
|
|
124
|
+
baseDir: to.baseDir,
|
|
125
|
+
serverPort: toPort,
|
|
126
|
+
publicServerUrl: `http://127.0.0.1:${toPort}`,
|
|
127
|
+
envPath: to.envPath,
|
|
128
|
+
env: {
|
|
129
|
+
...process.env,
|
|
130
|
+
...(toEphemeral ? { HAPPY_STACKS_EPHEMERAL_PORTS: '1', HAPPY_LOCAL_EPHEMERAL_PORTS: '1' } : {}),
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
await applyHappyServerMigrations({ serverDir: fullDir, env: { ...process.env, ...infra.env } });
|
|
134
|
+
|
|
135
135
|
// Copy sqlite DB to a snapshot so migration is consistent even if the source server is running.
|
|
136
136
|
const snapshotDir = join(to.baseDir, 'migrations');
|
|
137
137
|
await mkdir(snapshotDir, { recursive: true });
|
|
@@ -280,7 +280,7 @@ async function main() {
|
|
|
280
280
|
throw new Error('[migrate] --to-stack is required');
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
-
const rootDir = (await import('./utils/paths.mjs')).getRootDir(import.meta.url);
|
|
283
|
+
const rootDir = (await import('./utils/paths/paths.mjs')).getRootDir(import.meta.url);
|
|
284
284
|
await migrateLightToServer({ rootDir, fromStack, toStack, includeFiles, force, json });
|
|
285
285
|
}
|
|
286
286
|
|