diff --git a/bin/gstack-ios-qa-daemon b/bin/gstack-ios-qa-daemon index b0ca2c6af..d644a54a8 100755 --- a/bin/gstack-ios-qa-daemon +++ b/bin/gstack-ios-qa-daemon @@ -22,6 +22,35 @@ set -euo pipefail +case "${1:-}" in + -h|--help) + cat <<'EOF' +Usage: + gstack-ios-qa-daemon [--tailnet] + gstack-ios-qa-daemon --help + +Mac-side broker for /ios-qa and /ios-design-review. By default it binds a +loopback listener for a USB-connected iPhone running the in-app StateServer. +Pass --tailnet to also open the Tailscale listener after the local tailscaled +identity probe succeeds. + +Options: + --tailnet Enable the authenticated tailnet listener in addition to loopback. + -h, --help Print this help and exit without starting the daemon. + +Environment: + GSTACK_IOS_DAEMON_PORT Loopback listener port. Default: 9099. + GSTACK_IOS_TARGET_UDID Target iOS device UDID. Optional. + GSTACK_IOS_TARGET_BUNDLE_ID Bundle ID hosting StateServer. Default: com.gstack.iosqa.fixture. + +Readiness: + The daemon prints "READY: port= pid=" once the listener is bound. + Probe local health with: curl -sf http://127.0.0.1:${GSTACK_IOS_DAEMON_PORT:-9099}/healthz +EOF + exit 0 + ;; +esac + SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" GSTACK_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" ENTRY="$GSTACK_DIR/ios-qa/daemon/src/index.ts" diff --git a/test/ios-qa-daemon-cli.test.ts b/test/ios-qa-daemon-cli.test.ts new file mode 100644 index 000000000..66b01a61c --- /dev/null +++ b/test/ios-qa-daemon-cli.test.ts @@ -0,0 +1,38 @@ +/** + * G6 rider (#1932): `gstack-ios-qa-daemon --help` started the daemon (it + * exec'd bun with every argument). Help must print and exit without ever + * reaching the runtime. A stub `bun` first on PATH records any launch. + */ +import { describe, expect, test } from 'bun:test'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; + +const ROOT = path.resolve(import.meta.dir, '..'); +const DAEMON = `${ROOT}/bin/gstack-ios-qa-daemon`; + +describe('gstack-ios-qa-daemon --help', () => { + for (const flag of ['--help', '-h']) { + test(`${flag} prints usage and never starts the daemon`, () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ios-qa-daemon-help-')); + try { + const marker = path.join(dir, 'daemon-started'); + fs.writeFileSync(path.join(dir, 'bun'), `#!/usr/bin/env bash\necho started > "${marker}"\n`, { mode: 0o755 }); + const r = Bun.spawnSync(['bash', DAEMON, flag], { + env: { ...process.env, PATH: `${dir}${path.delimiter}${process.env.PATH ?? ''}`, HOME: dir }, + stdout: 'pipe', + stderr: 'pipe', + timeout: 10_000, + }); + expect(r.exitCode).toBe(0); + const out = r.stdout.toString(); + expect(out).toContain('Usage:'); + expect(out).toContain('--tailnet'); + expect(out).toContain('GSTACK_IOS_DAEMON_PORT'); + expect(fs.existsSync(marker)).toBe(false); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } + }); + } +});