@happier-dev/stack 0.1.0-preview.17.1 → 0.1.0-preview.21.1
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/docs/server-flavors.md +6 -6
- package/node_modules/@happier-dev/cli-common/dist/index.d.ts +2 -0
- package/node_modules/@happier-dev/cli-common/dist/index.d.ts.map +1 -1
- package/node_modules/@happier-dev/cli-common/dist/index.js +2 -0
- package/node_modules/@happier-dev/cli-common/dist/index.js.map +1 -1
- package/node_modules/@happier-dev/cli-common/dist/providers/index.d.ts +51 -0
- package/node_modules/@happier-dev/cli-common/dist/providers/index.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/providers/index.js +129 -0
- package/node_modules/@happier-dev/cli-common/dist/providers/index.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/service/index.d.ts +5 -0
- package/node_modules/@happier-dev/cli-common/dist/service/index.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/service/index.js +5 -0
- package/node_modules/@happier-dev/cli-common/dist/service/index.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/service/launchd.d.ts +19 -0
- package/node_modules/@happier-dev/cli-common/dist/service/launchd.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/service/launchd.js +117 -0
- package/node_modules/@happier-dev/cli-common/dist/service/launchd.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/service/manager.d.ts +55 -0
- package/node_modules/@happier-dev/cli-common/dist/service/manager.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/service/manager.js +302 -0
- package/node_modules/@happier-dev/cli-common/dist/service/manager.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/service/systemd.d.ts +12 -0
- package/node_modules/@happier-dev/cli-common/dist/service/systemd.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/service/systemd.js +75 -0
- package/node_modules/@happier-dev/cli-common/dist/service/systemd.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/service/windows.d.ts +8 -0
- package/node_modules/@happier-dev/cli-common/dist/service/windows.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/service/windows.js +29 -0
- package/node_modules/@happier-dev/cli-common/dist/service/windows.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/package.json +11 -0
- package/node_modules/@happier-dev/release-runtime/dist/assets.d.ts +22 -0
- package/node_modules/@happier-dev/release-runtime/dist/assets.d.ts.map +1 -0
- package/node_modules/@happier-dev/release-runtime/dist/assets.js +44 -0
- package/node_modules/@happier-dev/release-runtime/dist/assets.js.map +1 -0
- package/node_modules/@happier-dev/release-runtime/dist/checksums.d.ts +5 -0
- package/node_modules/@happier-dev/release-runtime/dist/checksums.d.ts.map +1 -0
- package/node_modules/@happier-dev/release-runtime/dist/checksums.js +21 -0
- package/node_modules/@happier-dev/release-runtime/dist/checksums.js.map +1 -0
- package/node_modules/@happier-dev/release-runtime/dist/extractPlan.d.ts +14 -0
- package/node_modules/@happier-dev/release-runtime/dist/extractPlan.d.ts.map +1 -0
- package/node_modules/@happier-dev/release-runtime/dist/extractPlan.js +39 -0
- package/node_modules/@happier-dev/release-runtime/dist/extractPlan.js.map +1 -0
- package/node_modules/@happier-dev/release-runtime/dist/github.d.ts +20 -0
- package/node_modules/@happier-dev/release-runtime/dist/github.d.ts.map +1 -0
- package/node_modules/@happier-dev/release-runtime/dist/github.js +60 -0
- package/node_modules/@happier-dev/release-runtime/dist/github.js.map +1 -0
- package/node_modules/@happier-dev/release-runtime/dist/index.d.ts +7 -0
- package/node_modules/@happier-dev/release-runtime/dist/index.d.ts.map +1 -0
- package/node_modules/@happier-dev/release-runtime/dist/index.js +7 -0
- package/node_modules/@happier-dev/release-runtime/dist/index.js.map +1 -0
- package/node_modules/@happier-dev/release-runtime/dist/minisign.d.ts +7 -0
- package/node_modules/@happier-dev/release-runtime/dist/minisign.d.ts.map +1 -0
- package/node_modules/@happier-dev/release-runtime/dist/minisign.js +92 -0
- package/node_modules/@happier-dev/release-runtime/dist/minisign.js.map +1 -0
- package/node_modules/@happier-dev/release-runtime/dist/verifiedDownload.d.ts +26 -0
- package/node_modules/@happier-dev/release-runtime/dist/verifiedDownload.d.ts.map +1 -0
- package/node_modules/@happier-dev/release-runtime/dist/verifiedDownload.js +53 -0
- package/node_modules/@happier-dev/release-runtime/dist/verifiedDownload.js.map +1 -0
- package/node_modules/@happier-dev/release-runtime/package.json +38 -0
- package/package.json +4 -2
- package/scripts/auth.mjs +3 -2
- package/scripts/auth_copy_from_pglite_lock_in_use.integration.test.mjs +1 -0
- package/scripts/auth_copy_from_runCapture.integration.test.mjs +8 -1
- package/scripts/auth_login_guided_server_no_expo.test.mjs +2 -0
- package/scripts/build.mjs +3 -18
- package/scripts/bundleWorkspaceDeps.mjs +5 -1
- package/scripts/bundleWorkspaceDeps.test.mjs +42 -1
- package/scripts/mobile.mjs +30 -2
- package/scripts/mobile_dev_client.mjs +7 -32
- package/scripts/mobile_dev_client_help_smoke.test.mjs +24 -0
- package/scripts/mobile_prebuild_happyDir_defined.test.mjs +47 -0
- package/scripts/mobile_prebuild_sets_rct_metro_port.test.mjs +81 -0
- package/scripts/mobile_run_ios_passes_port.integration.test.mjs +103 -0
- package/scripts/mobile_run_ios_uses_long_port_flag.test.mjs +106 -0
- package/scripts/providers_cmd.mjs +262 -0
- package/scripts/release_binary_smoke.integration.test.mjs +45 -37
- package/scripts/remote_cmd.mjs +352 -0
- package/scripts/self_host_daemon.real.integration.test.mjs +296 -0
- package/scripts/self_host_launchd.real.integration.test.mjs +211 -0
- package/scripts/self_host_runtime.mjs +1829 -327
- package/scripts/self_host_runtime.test.mjs +523 -1
- package/scripts/self_host_schtasks.real.integration.test.mjs +217 -0
- package/scripts/self_host_service_e2e_harness.mjs +93 -0
- package/scripts/self_host_systemd.real.integration.test.mjs +8 -86
- package/scripts/service.mjs +156 -26
- package/scripts/stack/command_arguments.mjs +1 -0
- package/scripts/stack/help_text.mjs +3 -1
- package/scripts/stack.mjs +2 -1
- package/scripts/stack_daemon_cmd.integration.test.mjs +37 -0
- package/scripts/stack_happy_cmd.integration.test.mjs +36 -0
- package/scripts/stack_pr_help_cmd.test.mjs +38 -0
- package/scripts/stop.mjs +2 -3
- package/scripts/utils/auth/credentials_paths.mjs +9 -9
- package/scripts/utils/auth/credentials_paths.test.mjs +8 -0
- package/scripts/utils/auth/orchestrated_stack_auth_flow.mjs +64 -3
- package/scripts/utils/auth/stable_scope_id.mjs +1 -1
- package/scripts/utils/cli/cli_registry.mjs +21 -0
- package/scripts/utils/cli/progress.mjs +8 -1
- package/scripts/utils/cli/progress.test.mjs +43 -0
- package/scripts/utils/dev/expo_dev.buildEnv.test.mjs +17 -0
- package/scripts/utils/dev/expo_dev.mjs +35 -5
- package/scripts/utils/dev/expo_dev_restart_port_reservation.test.mjs +180 -1
- package/scripts/utils/dev/expo_dev_runtime_metadata.test.mjs +126 -0
- package/scripts/utils/dev/expo_dev_verbose_logs.test.mjs +9 -2
- package/scripts/utils/mobile/dev_client_install_invocation.mjs +68 -0
- package/scripts/utils/mobile/dev_client_install_invocation.test.mjs +27 -0
- package/scripts/utils/server/port.mjs +20 -2
- package/scripts/utils/service/service_manager.definition.test.mjs +66 -0
- package/scripts/utils/service/service_manager.mjs +96 -0
- package/scripts/utils/service/service_manager.plan.test.mjs +37 -0
- package/scripts/utils/service/service_manager.test.mjs +20 -0
- package/scripts/utils/service/systemd_service_unit.mjs +1 -0
- package/scripts/utils/service/systemd_service_unit.test.mjs +42 -0
- package/scripts/utils/service/windows_schtasks_wrapper.mjs +1 -0
- package/scripts/utils/service/windows_schtasks_wrapper.test.mjs +25 -0
- package/scripts/utils/ui/ui_export_env.mjs +29 -0
- package/scripts/utils/ui/ui_export_env.test.mjs +25 -0
- package/scripts/worktrees.mjs +3 -0
- package/scripts/worktrees_status_default_target.test.mjs +56 -0
package/scripts/service.mjs
CHANGED
|
@@ -3,9 +3,11 @@ import { run, runCapture } from './utils/proc/proc.mjs';
|
|
|
3
3
|
import { getComponentDir, getDefaultAutostartPaths, getRootDir, getSystemdUnitInfo, resolveStackEnvPath } from './utils/paths/paths.mjs';
|
|
4
4
|
import { getInternalServerUrl, getPublicServerUrlEnvOverride } from './utils/server/urls.mjs';
|
|
5
5
|
import { ensureMacAutostartDisabled, ensureMacAutostartEnabled } from './utils/service/autostart_darwin.mjs';
|
|
6
|
+
import { installService as installManagedService, uninstallService as uninstallManagedService } from './utils/service/service_manager.mjs';
|
|
6
7
|
import { getCanonicalHomeDir } from './utils/env/config.mjs';
|
|
7
8
|
import { isSandboxed, sandboxAllowsGlobalSideEffects } from './utils/env/sandbox.mjs';
|
|
8
9
|
import { expandHome } from './utils/paths/canonical_home.mjs';
|
|
10
|
+
import { resolveInstalledCliRoot, resolveInstalledPath } from './utils/paths/runtime.mjs';
|
|
9
11
|
import { spawn } from 'node:child_process';
|
|
10
12
|
import { homedir } from 'node:os';
|
|
11
13
|
import { existsSync } from 'node:fs';
|
|
@@ -109,6 +111,33 @@ function ensureLinuxSystemModeSupported({ mode }) {
|
|
|
109
111
|
}
|
|
110
112
|
}
|
|
111
113
|
|
|
114
|
+
async function resolveStackAutostartProgramArgs({ rootDir, mode, systemUser }) {
|
|
115
|
+
const base = getCanonicalHomeDir();
|
|
116
|
+
const candidates =
|
|
117
|
+
process.platform === 'win32'
|
|
118
|
+
? [join(base, 'bin', 'hstack.exe'), join(base, 'bin', 'hstack.cmd'), join(base, 'bin', 'hstack')]
|
|
119
|
+
: [join(base, 'bin', 'hstack')];
|
|
120
|
+
|
|
121
|
+
let shimPath = candidates.find((p) => existsSync(p)) ?? '';
|
|
122
|
+
if (mode === 'system' && systemUser) {
|
|
123
|
+
const home = await resolveHomeDirForUser(systemUser);
|
|
124
|
+
if (home) {
|
|
125
|
+
const systemCandidates =
|
|
126
|
+
process.platform === 'win32'
|
|
127
|
+
? [
|
|
128
|
+
join(home, '.happier-stack', 'bin', 'hstack.exe'),
|
|
129
|
+
join(home, '.happier-stack', 'bin', 'hstack.cmd'),
|
|
130
|
+
join(home, '.happier-stack', 'bin', 'hstack'),
|
|
131
|
+
]
|
|
132
|
+
: [join(home, '.happier-stack', 'bin', 'hstack')];
|
|
133
|
+
shimPath = systemCandidates.find((p) => existsSync(p)) ?? shimPath;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (shimPath) return [shimPath, 'start'];
|
|
138
|
+
return [process.execPath, resolveInstalledPath(rootDir, 'bin/hstack.mjs'), 'start'];
|
|
139
|
+
}
|
|
140
|
+
|
|
112
141
|
export async function installService({ mode = 'user', systemUser = null } = {}) {
|
|
113
142
|
if (isSandboxed() && !sandboxAllowsGlobalSideEffects()) {
|
|
114
143
|
throw new Error(
|
|
@@ -118,11 +147,8 @@ export async function installService({ mode = 'user', systemUser = null } = {})
|
|
|
118
147
|
);
|
|
119
148
|
}
|
|
120
149
|
ensureLinuxSystemModeSupported({ mode });
|
|
121
|
-
if (process.platform !== 'darwin' && process.platform !== 'linux') {
|
|
122
|
-
throw new Error('[local] service install is only supported on macOS (launchd) and Linux (systemd).');
|
|
123
|
-
}
|
|
124
150
|
const rootDir = getRootDir(import.meta.url);
|
|
125
|
-
const { label } = getDefaultAutostartPaths();
|
|
151
|
+
const { label, stdoutPath, stderrPath, baseDir } = getDefaultAutostartPaths();
|
|
126
152
|
const env = getAutostartEnv({ rootDir, mode });
|
|
127
153
|
// Ensure the env file exists so the service never points at a missing path.
|
|
128
154
|
try {
|
|
@@ -137,17 +163,43 @@ export async function installService({ mode = 'user', systemUser = null } = {})
|
|
|
137
163
|
} catch {
|
|
138
164
|
// ignore
|
|
139
165
|
}
|
|
166
|
+
const programArgs = await resolveStackAutostartProgramArgs({ rootDir, mode, systemUser });
|
|
167
|
+
const workingDirectory =
|
|
168
|
+
process.platform === 'linux'
|
|
169
|
+
? '%h'
|
|
170
|
+
: process.platform === 'darwin'
|
|
171
|
+
? resolveInstalledCliRoot(rootDir)
|
|
172
|
+
: baseDir;
|
|
173
|
+
|
|
174
|
+
await installManagedService({
|
|
175
|
+
platform: process.platform,
|
|
176
|
+
mode,
|
|
177
|
+
homeDir: homedir(),
|
|
178
|
+
spec: {
|
|
179
|
+
label,
|
|
180
|
+
description: `Happier Stack (${label})`,
|
|
181
|
+
programArgs,
|
|
182
|
+
workingDirectory,
|
|
183
|
+
env,
|
|
184
|
+
runAsUser: mode === 'system' && systemUser ? systemUser : '',
|
|
185
|
+
stdoutPath,
|
|
186
|
+
stderrPath,
|
|
187
|
+
},
|
|
188
|
+
persistent: true,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
if (process.platform === 'win32') {
|
|
192
|
+
console.log(`${green('✓')} service installed ${dim('(Windows scheduled task)')}`);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
140
195
|
if (process.platform === 'darwin') {
|
|
141
|
-
await ensureMacAutostartEnabled({ rootDir, label, env });
|
|
142
196
|
console.log(`${green('✓')} service installed ${dim('(macOS launchd)')}`);
|
|
143
197
|
return;
|
|
144
198
|
}
|
|
145
199
|
if (mode === 'system') {
|
|
146
|
-
await ensureSystemdSystemServiceEnabled({ rootDir, label, env, systemUser });
|
|
147
200
|
console.log(`${green('✓')} service installed ${dim('(Linux systemd system)')}`);
|
|
148
201
|
return;
|
|
149
202
|
}
|
|
150
|
-
await ensureSystemdUserServiceEnabled({ rootDir, label, env });
|
|
151
203
|
console.log(`${green('✓')} service installed ${dim('(Linux systemd --user)')}`);
|
|
152
204
|
}
|
|
153
205
|
|
|
@@ -157,25 +209,40 @@ export async function uninstallService({ mode = 'user' } = {}) {
|
|
|
157
209
|
return;
|
|
158
210
|
}
|
|
159
211
|
ensureLinuxSystemModeSupported({ mode });
|
|
160
|
-
|
|
212
|
+
const rootDir = getRootDir(import.meta.url);
|
|
213
|
+
const { label, stdoutPath, stderrPath, baseDir } = getDefaultAutostartPaths();
|
|
214
|
+
const env = getAutostartEnv({ rootDir, mode });
|
|
215
|
+
const programArgs = await resolveStackAutostartProgramArgs({ rootDir, mode, systemUser: null });
|
|
216
|
+
const workingDirectory =
|
|
217
|
+
process.platform === 'linux'
|
|
218
|
+
? '%h'
|
|
219
|
+
: process.platform === 'darwin'
|
|
220
|
+
? resolveInstalledCliRoot(rootDir)
|
|
221
|
+
: baseDir;
|
|
222
|
+
|
|
223
|
+
await uninstallManagedService({
|
|
224
|
+
platform: process.platform,
|
|
225
|
+
mode,
|
|
226
|
+
homeDir: homedir(),
|
|
227
|
+
spec: {
|
|
228
|
+
label,
|
|
229
|
+
description: `Happier Stack (${label})`,
|
|
230
|
+
programArgs,
|
|
231
|
+
workingDirectory,
|
|
232
|
+
env,
|
|
233
|
+
stdoutPath,
|
|
234
|
+
stderrPath,
|
|
235
|
+
},
|
|
236
|
+
persistent: true,
|
|
237
|
+
});
|
|
161
238
|
|
|
162
|
-
if (process.platform === '
|
|
163
|
-
|
|
164
|
-
await ensureSystemdSystemServiceDisabled({ remove: true });
|
|
165
|
-
console.log(`${green('✓')} service uninstalled ${dim('(systemd system unit removed)')}`);
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
await ensureSystemdUserServiceDisabled({ remove: true });
|
|
169
|
-
console.log(`${green('✓')} service uninstalled ${dim('(systemd user unit removed)')}`);
|
|
239
|
+
if (process.platform === 'win32') {
|
|
240
|
+
console.log(`${green('✓')} service uninstalled ${dim('(Windows task removed)')}`);
|
|
170
241
|
return;
|
|
171
242
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
try {
|
|
176
|
-
await rm(plistPath, { force: true });
|
|
177
|
-
} catch {
|
|
178
|
-
// ignore
|
|
243
|
+
if (process.platform === 'linux') {
|
|
244
|
+
console.log(`${green('✓')} service uninstalled ${dim('(systemd unit removed)')}`);
|
|
245
|
+
return;
|
|
179
246
|
}
|
|
180
247
|
console.log(`${green('✓')} service uninstalled ${dim('(plist removed)')}`);
|
|
181
248
|
}
|
|
@@ -633,8 +700,8 @@ async function main() {
|
|
|
633
700
|
const systemUser = resolveSystemUser(helpScopeArgv);
|
|
634
701
|
ensureLinuxSystemModeSupported({ mode });
|
|
635
702
|
|
|
636
|
-
if (process.platform !== 'darwin' && process.platform !== 'linux') {
|
|
637
|
-
throw new Error('[local] service commands are only supported on macOS (launchd)
|
|
703
|
+
if (process.platform !== 'darwin' && process.platform !== 'linux' && process.platform !== 'win32') {
|
|
704
|
+
throw new Error('[local] service commands are only supported on macOS (launchd), Linux (systemd), and Windows (schtasks).');
|
|
638
705
|
}
|
|
639
706
|
const positionals = helpScopeArgv.filter((a) => a && a !== '--' && !a.startsWith('-'));
|
|
640
707
|
const cmd = positionals[0] ?? 'help';
|
|
@@ -711,7 +778,17 @@ async function main() {
|
|
|
711
778
|
health = { ok: false, status: null, body: null };
|
|
712
779
|
}
|
|
713
780
|
|
|
714
|
-
if (process.platform === '
|
|
781
|
+
if (process.platform === 'win32') {
|
|
782
|
+
const { label, stdoutPath, stderrPath } = getDefaultAutostartPaths();
|
|
783
|
+
const taskName = `Happier\\${label}`;
|
|
784
|
+
let schtasksStatus = null;
|
|
785
|
+
try {
|
|
786
|
+
schtasksStatus = await runCapture('schtasks', ['/Query', '/TN', taskName, '/FO', 'LIST', '/V']);
|
|
787
|
+
} catch (e) {
|
|
788
|
+
schtasksStatus = e && typeof e === 'object' && 'out' in e ? e.out : null;
|
|
789
|
+
}
|
|
790
|
+
printResult({ json, data: { label, taskName, stdoutPath, stderrPath, internalUrl, schtasksStatus, health } });
|
|
791
|
+
} else if (process.platform === 'darwin') {
|
|
715
792
|
const { plistPath, stdoutPath, stderrPath, label } = getDefaultAutostartPaths();
|
|
716
793
|
let launchctlLine = null;
|
|
717
794
|
try {
|
|
@@ -736,6 +813,11 @@ async function main() {
|
|
|
736
813
|
printResult({ json, data: { mode, unitName, unitPath, internalUrl, systemctlStatus, health } });
|
|
737
814
|
}
|
|
738
815
|
} else {
|
|
816
|
+
if (process.platform === 'win32') {
|
|
817
|
+
const { label } = getDefaultAutostartPaths();
|
|
818
|
+
await run('schtasks', ['/Query', '/TN', `Happier\\${label}`]);
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
739
821
|
if (process.platform === 'darwin') {
|
|
740
822
|
await showStatus();
|
|
741
823
|
} else {
|
|
@@ -749,6 +831,13 @@ async function main() {
|
|
|
749
831
|
}
|
|
750
832
|
return;
|
|
751
833
|
case 'start':
|
|
834
|
+
if (process.platform === 'win32') {
|
|
835
|
+
const { label } = getDefaultAutostartPaths();
|
|
836
|
+
await run('schtasks', ['/Run', '/TN', `Happier\\${label}`]).catch(() => {});
|
|
837
|
+
await postStartDiagnostics();
|
|
838
|
+
if (json) printResult({ json, data: { ok: true, action: 'start' } });
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
752
841
|
if (process.platform === 'darwin') {
|
|
753
842
|
await startLaunchAgent({ persistent: false });
|
|
754
843
|
} else {
|
|
@@ -763,6 +852,12 @@ async function main() {
|
|
|
763
852
|
if (json) printResult({ json, data: { ok: true, action: 'start' } });
|
|
764
853
|
return;
|
|
765
854
|
case 'stop':
|
|
855
|
+
if (process.platform === 'win32') {
|
|
856
|
+
const { label } = getDefaultAutostartPaths();
|
|
857
|
+
await run('schtasks', ['/End', '/TN', `Happier\\${label}`]).catch(() => {});
|
|
858
|
+
if (json) printResult({ json, data: { ok: true, action: 'stop' } });
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
766
861
|
if (process.platform === 'darwin') {
|
|
767
862
|
await stopLaunchAgent({ persistent: false });
|
|
768
863
|
} else {
|
|
@@ -776,6 +871,14 @@ async function main() {
|
|
|
776
871
|
if (json) printResult({ json, data: { ok: true, action: 'stop' } });
|
|
777
872
|
return;
|
|
778
873
|
case 'restart':
|
|
874
|
+
if (process.platform === 'win32') {
|
|
875
|
+
const { label } = getDefaultAutostartPaths();
|
|
876
|
+
await run('schtasks', ['/End', '/TN', `Happier\\${label}`]).catch(() => {});
|
|
877
|
+
await run('schtasks', ['/Run', '/TN', `Happier\\${label}`]).catch(() => {});
|
|
878
|
+
await postStartDiagnostics();
|
|
879
|
+
if (json) printResult({ json, data: { ok: true, action: 'restart' } });
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
779
882
|
if (process.platform === 'darwin') {
|
|
780
883
|
if (!(await restartLaunchAgentBestEffort())) {
|
|
781
884
|
await stopLaunchAgent({ persistent: false });
|
|
@@ -794,6 +897,14 @@ async function main() {
|
|
|
794
897
|
if (json) printResult({ json, data: { ok: true, action: 'restart' } });
|
|
795
898
|
return;
|
|
796
899
|
case 'enable':
|
|
900
|
+
if (process.platform === 'win32') {
|
|
901
|
+
const { label } = getDefaultAutostartPaths();
|
|
902
|
+
await run('schtasks', ['/Change', '/TN', `Happier\\${label}`, '/Enable']).catch(() => {});
|
|
903
|
+
await run('schtasks', ['/Run', '/TN', `Happier\\${label}`]).catch(() => {});
|
|
904
|
+
await postStartDiagnostics();
|
|
905
|
+
if (json) printResult({ json, data: { ok: true, action: 'enable' } });
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
797
908
|
if (process.platform === 'darwin') {
|
|
798
909
|
await startLaunchAgent({ persistent: true });
|
|
799
910
|
} else {
|
|
@@ -808,6 +919,13 @@ async function main() {
|
|
|
808
919
|
if (json) printResult({ json, data: { ok: true, action: 'enable' } });
|
|
809
920
|
return;
|
|
810
921
|
case 'disable':
|
|
922
|
+
if (process.platform === 'win32') {
|
|
923
|
+
const { label } = getDefaultAutostartPaths();
|
|
924
|
+
await run('schtasks', ['/End', '/TN', `Happier\\${label}`]).catch(() => {});
|
|
925
|
+
await run('schtasks', ['/Change', '/TN', `Happier\\${label}`, '/Disable']).catch(() => {});
|
|
926
|
+
if (json) printResult({ json, data: { ok: true, action: 'disable' } });
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
811
929
|
if (process.platform === 'darwin') {
|
|
812
930
|
await stopLaunchAgent({ persistent: true });
|
|
813
931
|
} else {
|
|
@@ -821,6 +939,15 @@ async function main() {
|
|
|
821
939
|
if (json) printResult({ json, data: { ok: true, action: 'disable' } });
|
|
822
940
|
return;
|
|
823
941
|
case 'logs':
|
|
942
|
+
if (process.platform === 'win32') {
|
|
943
|
+
const { stdoutPath, stderrPath } = getDefaultAutostartPaths();
|
|
944
|
+
const out = await readLastLines(stdoutPath, 200).catch(() => '');
|
|
945
|
+
const err = await readLastLines(stderrPath, 200).catch(() => '');
|
|
946
|
+
console.log(bullets([kv('stdout:', stdoutPath), kv('stderr:', stderrPath)]));
|
|
947
|
+
if (out.trim()) console.log(out.trimEnd());
|
|
948
|
+
if (err.trim()) console.log(err.trimEnd());
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
824
951
|
if (process.platform === 'darwin') {
|
|
825
952
|
await showLogs();
|
|
826
953
|
} else {
|
|
@@ -834,6 +961,9 @@ async function main() {
|
|
|
834
961
|
}
|
|
835
962
|
return;
|
|
836
963
|
case 'tail':
|
|
964
|
+
if (process.platform === 'win32') {
|
|
965
|
+
throw new Error('[local] service tail is not supported on Windows yet. Use: hstack service logs');
|
|
966
|
+
}
|
|
837
967
|
if (process.platform === 'darwin') {
|
|
838
968
|
await tailLogs();
|
|
839
969
|
} else {
|
|
@@ -6,10 +6,11 @@ const STACK_HELP_USAGE_LINES = [
|
|
|
6
6
|
'hstack stack archive <name> [--dry-run] [--date=YYYY-MM-DD] [--json]',
|
|
7
7
|
'hstack stack duplicate <from> <to> [--duplicate-worktrees] [--deps=none|link|install|link-or-install] [--json]',
|
|
8
8
|
'hstack stack info <name> [--json]',
|
|
9
|
-
'hstack stack pr <name> --repo=<pr-url|number> [--server-flavor=light|full] [--dev|--start] [--json] [-- ...]',
|
|
9
|
+
'hstack stack pr <name> --repo=<pr-url|number> [--server-flavor=light|full] [--dev|--start] [--reuse] [--update] [--force] [--background] [--mobile] [--expo-tailscale] [--json] [-- ...]',
|
|
10
10
|
'hstack stack create-dev-auth-seed [name] [--server=happier-server|happier-server-light] [--login|--no-login] [--skip-default-seed] [--non-interactive] [--json]',
|
|
11
11
|
'hstack stack daemon <name> start|stop|restart|status [--json]',
|
|
12
12
|
'hstack stack happier <name> [-- ...]',
|
|
13
|
+
'hstack stack bug-report <name> [-- ...]',
|
|
13
14
|
'hstack stack env <name> set KEY=VALUE [KEY2=VALUE2...] | unset KEY [KEY2...] | get KEY | list | path [--json]',
|
|
14
15
|
'hstack stack auth <name> status|login|copy-from [--json]',
|
|
15
16
|
'hstack stack dev <name> [-- ...]',
|
|
@@ -48,6 +49,7 @@ export const STACK_HELP_COMMANDS = [
|
|
|
48
49
|
'daemon',
|
|
49
50
|
'eas',
|
|
50
51
|
'happier',
|
|
52
|
+
'bug-report',
|
|
51
53
|
'env',
|
|
52
54
|
'auth',
|
|
53
55
|
'dev',
|
package/scripts/stack.mjs
CHANGED
|
@@ -1622,7 +1622,7 @@ async function cmdPrStack({ rootDir, argv }) {
|
|
|
1622
1622
|
'[stack] usage:',
|
|
1623
1623
|
' hstack stack pr <name> --repo=<pr-url|number> [--dev|--start]',
|
|
1624
1624
|
' [--seed-auth] [--copy-auth-from=<stack>] [--link-auth] [--with-infra] [--auth-force]',
|
|
1625
|
-
' [--remote=upstream] [--deps=none|link|install|link-or-install] [--update] [--force] [--background]',
|
|
1625
|
+
' [--remote=upstream] [--deps=none|link|install|link-or-install] [--reuse] [--update] [--force] [--background]',
|
|
1626
1626
|
' [--mobile] # also start Expo dev-client Metro for mobile',
|
|
1627
1627
|
' [--expo-tailscale] # forward Expo to Tailscale interface for remote access',
|
|
1628
1628
|
' [--json] [-- <stack dev/start args...>]',
|
|
@@ -1639,6 +1639,7 @@ async function cmdPrStack({ rootDir, argv }) {
|
|
|
1639
1639
|
'',
|
|
1640
1640
|
'notes:',
|
|
1641
1641
|
' - This composes existing commands: `hstack stack new`, `hstack stack wt ...`, and `hstack stack auth ...`',
|
|
1642
|
+
' - `--reuse` reuses an existing PR stack (otherwise `stack pr` fails closed if the stack already exists)',
|
|
1642
1643
|
' - For auth seeding, pass `--seed-auth` and optionally `--copy-auth-from=dev-auth` (or main)',
|
|
1643
1644
|
' - `--link-auth` symlinks auth files instead of copying (keeps credentials in sync, but reduces isolation)',
|
|
1644
1645
|
].join('\n'),
|
|
@@ -448,6 +448,43 @@ test('hstack stack daemon <name> start uses runtime server port when env port is
|
|
|
448
448
|
);
|
|
449
449
|
});
|
|
450
450
|
|
|
451
|
+
test('hstack stack daemon <name> start uses explicit HAPPIER_SERVER_URL when env port and runtime port are missing', async (t) => {
|
|
452
|
+
const fixture = await createDaemonFixture(t, {
|
|
453
|
+
prefix: 'happy-stacks-stack-daemon-explicit-server-url-',
|
|
454
|
+
stackName: 'exp-test',
|
|
455
|
+
serverPort: 4101,
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
await writeDummyAuth({ cliHomeDir: fixture.stackCliHome });
|
|
459
|
+
|
|
460
|
+
const explicitPort = fixture.serverPort + 9;
|
|
461
|
+
const envPath = join(fixture.storageDir, fixture.stackName, 'env');
|
|
462
|
+
await mkdir(dirname(envPath), { recursive: true });
|
|
463
|
+
await writeFile(
|
|
464
|
+
envPath,
|
|
465
|
+
[
|
|
466
|
+
`HAPPIER_STACK_REPO_DIR=${fixture.baseEnv.HAPPIER_STACK_WORKSPACE_DIR}/happier`,
|
|
467
|
+
`HAPPIER_STACK_CLI_HOME_DIR=${fixture.stackCliHome}`,
|
|
468
|
+
`HAPPIER_SERVER_URL=http://127.0.0.1:${explicitPort}`,
|
|
469
|
+
`HAPPIER_WEBAPP_URL=http://happier-exp-test.localhost:${explicitPort}`,
|
|
470
|
+
'',
|
|
471
|
+
].join('\n'),
|
|
472
|
+
'utf-8'
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
registerDaemonCleanup(t, { env: fixture.baseEnv, stackName: fixture.stackName });
|
|
476
|
+
|
|
477
|
+
const startRes = await runHstack(['stack', 'daemon', fixture.stackName, 'start', '--json'], { env: fixture.baseEnv });
|
|
478
|
+
assertExitOk(startRes, 'stack daemon start uses explicit HAPPIER_SERVER_URL');
|
|
479
|
+
|
|
480
|
+
const logPath = join(fixture.stackCliHome, 'stub-daemon.log');
|
|
481
|
+
const logText = await readLogText(logPath);
|
|
482
|
+
assert.ok(
|
|
483
|
+
logText.includes(`server_url=http://127.0.0.1:${explicitPort}`),
|
|
484
|
+
`expected daemon env to target explicit HAPPIER_SERVER_URL port ${explicitPort}\n${logText}`
|
|
485
|
+
);
|
|
486
|
+
});
|
|
487
|
+
|
|
451
488
|
test('hstack stack auth <name> login --identity=<name> --print prints identity-scoped HAPPIER_HOME_DIR', async (t) => {
|
|
452
489
|
const fixture = await createDaemonFixture(t, {
|
|
453
490
|
prefix: 'happier-stack-auth-identity-',
|
|
@@ -25,6 +25,7 @@ async function writeStubHappyCli({ cliDir, message }) {
|
|
|
25
25
|
[
|
|
26
26
|
`console.log(JSON.stringify({`,
|
|
27
27
|
` message: ${JSON.stringify(message)},`,
|
|
28
|
+
` args: process.argv.slice(2),`,
|
|
28
29
|
` stack: process.env.HAPPIER_STACK_STACK || null,`,
|
|
29
30
|
` envFile: process.env.HAPPIER_STACK_ENV_FILE || null,`,
|
|
30
31
|
` homeDir: process.env.HAPPIER_HOME_DIR || null,`,
|
|
@@ -261,3 +262,38 @@ test('hstack stack <name> happier ... stack-name-first shorthand works', async (
|
|
|
261
262
|
assert.equal(out.message, 'name-first');
|
|
262
263
|
assert.equal(out.stack, fixture.stackName);
|
|
263
264
|
});
|
|
265
|
+
|
|
266
|
+
test('hstack stack bug-report <name> forwards bug-report command under stack env', async (t) => {
|
|
267
|
+
const fixture = await createHappyStackFixture(t, {
|
|
268
|
+
prefix: 'happier-stack-stack-bug-report-',
|
|
269
|
+
message: 'bug-report-alias',
|
|
270
|
+
serverPort: 4099,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const res = await runNodeCapture(
|
|
274
|
+
[
|
|
275
|
+
join(rootDir, 'bin', 'hstack.mjs'),
|
|
276
|
+
'stack',
|
|
277
|
+
'bug-report',
|
|
278
|
+
fixture.stackName,
|
|
279
|
+
'--',
|
|
280
|
+
'--title',
|
|
281
|
+
'CLI bug',
|
|
282
|
+
'--summary',
|
|
283
|
+
'summary',
|
|
284
|
+
'--current-behavior',
|
|
285
|
+
'current',
|
|
286
|
+
'--expected-behavior',
|
|
287
|
+
'expected',
|
|
288
|
+
'--accept-privacy-notice',
|
|
289
|
+
'--no-include-diagnostics',
|
|
290
|
+
],
|
|
291
|
+
{ cwd: rootDir, env: fixture.baseEnv }
|
|
292
|
+
);
|
|
293
|
+
assert.equal(res.code, 0, `expected exit 0, got ${res.code}\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
294
|
+
|
|
295
|
+
const out = JSON.parse(res.stdout.trim());
|
|
296
|
+
assert.equal(out.message, 'bug-report-alias');
|
|
297
|
+
assert.equal(out.stack, fixture.stackName);
|
|
298
|
+
assert.equal(out.args[0], 'bug-report');
|
|
299
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
import { getStackRootFromMeta, hstackBinPath, runNodeCapture } from './testkit/auth_testkit.mjs';
|
|
6
|
+
|
|
7
|
+
test('hstack stack pr --help surfaces --reuse (supported flag)', async () => {
|
|
8
|
+
const rootDir = getStackRootFromMeta(import.meta.url);
|
|
9
|
+
|
|
10
|
+
const env = {
|
|
11
|
+
...process.env,
|
|
12
|
+
// Prevent env.mjs from auto-loading a real machine stack env file (keeps the test hermetic).
|
|
13
|
+
HAPPIER_STACK_STACK: 'test-stack',
|
|
14
|
+
HAPPIER_STACK_ENV_FILE: join(rootDir, 'scripts', 'nonexistent-env'),
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const res = await runNodeCapture([hstackBinPath(rootDir), 'stack', 'pr', '--help'], { cwd: rootDir, env });
|
|
18
|
+
assert.equal(res.code, 0, `expected exit 0, got ${res.code}\nstderr:\n${res.stderr}\nstdout:\n${res.stdout}`);
|
|
19
|
+
assert.match(res.stdout, /--reuse/, `expected stack pr help to include --reuse\nstdout:\n${res.stdout}`);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('hstack stack --help shows --reuse on the stack pr usage line', async () => {
|
|
23
|
+
const rootDir = getStackRootFromMeta(import.meta.url);
|
|
24
|
+
|
|
25
|
+
const env = {
|
|
26
|
+
...process.env,
|
|
27
|
+
HAPPIER_STACK_STACK: 'test-stack',
|
|
28
|
+
HAPPIER_STACK_ENV_FILE: join(rootDir, 'scripts', 'nonexistent-env'),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const res = await runNodeCapture([hstackBinPath(rootDir), 'stack', '--help'], { cwd: rootDir, env });
|
|
32
|
+
assert.equal(res.code, 0, `expected exit 0, got ${res.code}\nstderr:\n${res.stderr}\nstdout:\n${res.stdout}`);
|
|
33
|
+
assert.match(
|
|
34
|
+
res.stdout,
|
|
35
|
+
/hstack stack pr[^\n]*--reuse\b/,
|
|
36
|
+
`expected stack root help to include --reuse on the stack pr usage line\nstdout:\n${res.stdout}`
|
|
37
|
+
);
|
|
38
|
+
});
|
package/scripts/stop.mjs
CHANGED
|
@@ -57,7 +57,7 @@ async function listAllStackNames() {
|
|
|
57
57
|
async function main() {
|
|
58
58
|
const rootDir = getRootDir(import.meta.url);
|
|
59
59
|
const argv = process.argv.slice(2);
|
|
60
|
-
const { flags, kv } = parseArgs(argv);
|
|
60
|
+
const { flags, kv: argsKv } = parseArgs(argv);
|
|
61
61
|
const json = wantsJson(argv, { flags });
|
|
62
62
|
|
|
63
63
|
if (wantsHelp(argv, { flags })) {
|
|
@@ -65,7 +65,7 @@ async function main() {
|
|
|
65
65
|
return;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
const exceptStacks = new Set(parseCsv(
|
|
68
|
+
const exceptStacks = new Set(parseCsv(argsKv.get('--except-stacks')));
|
|
69
69
|
const yes = flags.has('--yes');
|
|
70
70
|
const aggressive = flags.has('--aggressive');
|
|
71
71
|
const sweepOwned = flags.has('--sweep-owned');
|
|
@@ -187,4 +187,3 @@ main().catch((err) => {
|
|
|
187
187
|
console.error('[stop] failed:', err);
|
|
188
188
|
process.exit(1);
|
|
189
189
|
});
|
|
190
|
-
|
|
@@ -8,12 +8,12 @@ function normalizeServerUrl(url) {
|
|
|
8
8
|
return String(url ?? '').trim().replace(/\/+$/, '');
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
function sanitizeServerIdForFilesystem(raw, fallback = '
|
|
11
|
+
function sanitizeServerIdForFilesystem(raw, fallback = 'default') {
|
|
12
12
|
const value = String(raw ?? '').trim();
|
|
13
|
-
if (!value) return String(fallback ?? '').trim() || '
|
|
14
|
-
if (value === '.' || value === '..') return String(fallback ?? '').trim() || '
|
|
15
|
-
if (value.includes('/') || value.includes('\\')) return String(fallback ?? '').trim() || '
|
|
16
|
-
if (!SERVER_ID_SAFE_RE.test(value)) return String(fallback ?? '').trim() || '
|
|
13
|
+
if (!value) return String(fallback ?? '').trim() || 'default';
|
|
14
|
+
if (value === '.' || value === '..') return String(fallback ?? '').trim() || 'default';
|
|
15
|
+
if (value.includes('/') || value.includes('\\')) return String(fallback ?? '').trim() || 'default';
|
|
16
|
+
if (!SERVER_ID_SAFE_RE.test(value)) return String(fallback ?? '').trim() || 'default';
|
|
17
17
|
return value;
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -46,8 +46,8 @@ export function resolveStackCredentialPaths({ cliHomeDir, serverUrl = '', env =
|
|
|
46
46
|
const legacyPath = join(home, 'access.key');
|
|
47
47
|
const normalizedServerUrl = normalizeServerUrl(serverUrl);
|
|
48
48
|
const urlHashServerId = sanitizeServerIdForFilesystem(
|
|
49
|
-
normalizedServerUrl ? deriveServerIdFromUrl(normalizedServerUrl) : '
|
|
50
|
-
'
|
|
49
|
+
normalizedServerUrl ? deriveServerIdFromUrl(normalizedServerUrl) : 'default',
|
|
50
|
+
'default'
|
|
51
51
|
);
|
|
52
52
|
const overrideServerId = resolveActiveServerIdOverride(env);
|
|
53
53
|
const activeServerId = overrideServerId || urlHashServerId;
|
|
@@ -71,8 +71,8 @@ export function resolveStackDaemonStatePaths({ cliHomeDir, serverUrl = '', env =
|
|
|
71
71
|
const home = requireCliHomeDir(cliHomeDir);
|
|
72
72
|
const normalizedServerUrl = normalizeServerUrl(serverUrl);
|
|
73
73
|
const urlHashServerId = sanitizeServerIdForFilesystem(
|
|
74
|
-
normalizedServerUrl ? deriveServerIdFromUrl(normalizedServerUrl) : '
|
|
75
|
-
'
|
|
74
|
+
normalizedServerUrl ? deriveServerIdFromUrl(normalizedServerUrl) : 'default',
|
|
75
|
+
'default'
|
|
76
76
|
);
|
|
77
77
|
const overrideServerId = resolveActiveServerIdOverride(env);
|
|
78
78
|
const activeServerId = overrideServerId || urlHashServerId;
|
|
@@ -22,6 +22,14 @@ test('resolveStackCredentialPaths returns legacy + server-scoped paths', async (
|
|
|
22
22
|
assert.deepEqual(out.paths, [out.serverScopedPath, out.legacyPath]);
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
+
test('resolveStackCredentialPaths uses a neutral default server id when serverUrl is empty', async () => {
|
|
26
|
+
const dir = await mkdtemp(join(tmpdir(), 'happy-stacks-cred-paths-'));
|
|
27
|
+
const out = resolveStackCredentialPaths({ cliHomeDir: dir, serverUrl: '' });
|
|
28
|
+
assert.equal(out.urlHashServerId, 'default');
|
|
29
|
+
assert.equal(out.activeServerId, 'default');
|
|
30
|
+
assert.ok(out.serverScopedPath.endsWith('/servers/default/access.key'));
|
|
31
|
+
});
|
|
32
|
+
|
|
25
33
|
test('findExistingStackCredentialPath prefers server-scoped credentials', async () => {
|
|
26
34
|
const dir = await mkdtemp(join(tmpdir(), 'happy-stacks-cred-paths-'));
|
|
27
35
|
const serverUrl = 'http://127.0.0.1:3009';
|
|
@@ -19,15 +19,76 @@ function appendCauseText(baseMessage, cause) {
|
|
|
19
19
|
return `${msg}\n\n[auth] Cause: ${c}`;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
async function readTextWithTimeout(path, { timeoutMs = 1200 } = {}) {
|
|
23
|
+
try {
|
|
24
|
+
const controller = new AbortController();
|
|
25
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
26
|
+
const res = await fetch(path, { signal: controller.signal });
|
|
27
|
+
clearTimeout(timer);
|
|
28
|
+
return { ok: res.ok, status: res.status };
|
|
29
|
+
} catch (error) {
|
|
30
|
+
return { ok: false, status: 0, error: error instanceof Error ? error.message : String(error) };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function formatPidStatus(label, pidRaw) {
|
|
35
|
+
const pid = Number(pidRaw);
|
|
36
|
+
if (!Number.isFinite(pid) || pid <= 1) return `[auth] ${label}: unavailable`;
|
|
37
|
+
return `[auth] ${label}: ${pid} (${isPidAlive(pid) ? 'alive' : 'stale/dead'})`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function formatLogPath(label, logPath) {
|
|
41
|
+
const p = String(logPath ?? '').trim();
|
|
42
|
+
return p ? `[auth] ${label}: ${p}` : `[auth] ${label}: unavailable`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function appendRuntimeHealthDiagnostics(message, stackName) {
|
|
46
|
+
const name = String(stackName ?? '').trim() || 'main';
|
|
47
|
+
const statePath = getStackRuntimeStatePath(name);
|
|
48
|
+
|
|
49
|
+
const state = await readStackRuntimeStateFile(statePath).catch(() => null);
|
|
50
|
+
if (!state || typeof state !== 'object') {
|
|
51
|
+
return `${message}\n\n[auth] Stack runtime state unavailable: ${statePath}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const serverPort = Number(state?.ports?.server);
|
|
55
|
+
let serverHealth = 'not configured';
|
|
56
|
+
if (Number.isFinite(serverPort) && serverPort > 0) {
|
|
57
|
+
const probe = await readTextWithTimeout(`http://127.0.0.1:${serverPort}/health`, { timeoutMs: 1200 });
|
|
58
|
+
if (probe.ok) {
|
|
59
|
+
serverHealth = `HTTP ${probe.status}`;
|
|
60
|
+
} else if (probe.error) {
|
|
61
|
+
serverHealth = probe.error;
|
|
62
|
+
} else {
|
|
63
|
+
serverHealth = `HTTP ${probe.status}`;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const runtimeSummary = [
|
|
68
|
+
`[auth] Stack runtime path: ${statePath}`,
|
|
69
|
+
formatPidStatus('ownerPid', state?.ownerPid),
|
|
70
|
+
formatPidStatus('serverPid', state?.processes?.serverPid),
|
|
71
|
+
formatPidStatus('expoPid', state?.processes?.expoPid),
|
|
72
|
+
formatPidStatus('expoTailscaleForwarderPid', state?.processes?.expoTailscaleForwarderPid),
|
|
73
|
+
`[auth] server port: ${Number.isFinite(serverPort) && serverPort > 0 ? serverPort : 'unconfigured'}`,
|
|
74
|
+
`[auth] server health: ${serverHealth}`,
|
|
75
|
+
formatLogPath('runner log', state?.logs?.runner),
|
|
76
|
+
formatLogPath('cli log', state?.logs?.cli),
|
|
77
|
+
].join('\n');
|
|
78
|
+
|
|
79
|
+
return `${String(message ?? '').trim()}\n\n${runtimeSummary}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
22
82
|
async function appendRunnerLogTailDiagnostics({ message, stackName, lines = 140 }) {
|
|
23
83
|
const base = String(message ?? '').trim();
|
|
24
84
|
const logPath = await resolveRunnerLogPathFromRuntime({ stackName, waitMs: 1000, pollMs: 100 }).catch(() => '');
|
|
25
|
-
|
|
85
|
+
const withState = await appendRuntimeHealthDiagnostics(base, stackName).catch(() => base);
|
|
86
|
+
if (!logPath) return withState;
|
|
26
87
|
const tail = await readLastLines(logPath, lines).catch(() => null);
|
|
27
88
|
if (!tail || !String(tail).trim()) {
|
|
28
|
-
return `${
|
|
89
|
+
return `${withState}\n\n[auth] Stack runner log: ${logPath}`;
|
|
29
90
|
}
|
|
30
|
-
return `${
|
|
91
|
+
return `${withState}\n\n[auth] Stack runner log: ${logPath}\n\n[auth] Last runner log lines:\n${String(tail).trimEnd()}`;
|
|
31
92
|
}
|
|
32
93
|
|
|
33
94
|
async function tryStartStackUiInBackgroundForAuth({ rootDir, stackName, env = process.env } = {}) {
|
|
@@ -50,7 +50,7 @@ export function buildStackStableScopeId({ stackName = 'main', cliIdentity = 'def
|
|
|
50
50
|
if (isScopeIdSafe(compact)) return compact;
|
|
51
51
|
|
|
52
52
|
const fallback = `stack_${hash}`;
|
|
53
|
-
return isScopeIdSafe(fallback) ? fallback : '
|
|
53
|
+
return isScopeIdSafe(fallback) ? fallback : 'stack-default';
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
export function isStableScopeDisabled(env = process.env) {
|