Migrate setup from bash scripts to cross-platform Node.js modules (#382)
* refactor: migrate setup from bash scripts to cross-platform Node.js modules Replace 9 bash scripts + qr-auth.html with a two-phase setup system: a bash bootstrap (setup.sh) for Node.js/npm verification, and TypeScript modules (src/setup/) for everything else. Resolves cross-platform issues: sed -i replaced with fs operations, sqlite3 CLI replaced with better-sqlite3, browser opening made cross-platform, service management supports launchd/ systemd/WSL nohup fallback, SQL injection prevented with parameterized queries. Add Linux systemctl equivalents alongside macOS launchctl commands in 8 skill files and CLAUDE.md. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: setup migration issues — pairing code, systemd fallback, nohup escaping - Emit WhatsApp pairing code immediately when received, before polling for auth completion. Previously the code was only shown in the final status block after auth succeeded — a catch-22 since the user needs the code to authenticate. (whatsapp-auth.ts) - Add systemd user session pre-check before attempting to write the user-level service unit. Falls back to nohup wrapper when user-level systemd is unavailable (e.g. su session without login/D-Bus). (service.ts) - Rewrite nohup wrapper template using array join instead of template literal to fix shell variable escaping (\\$ → $). (service.ts) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: detect stale docker group and kill orphaned processes on Linux systemd * fix: remove redundant shell option from execSync to fix TS2769 execSync already runs in a shell by default; the explicit `shell: true` caused a type error with @types/node which expects string, not boolean. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: hide QR browser auth option on headless Linux Emit IS_HEADLESS from environment step and condition SKILL.md to only show pairing code + QR terminal when no display server is available (headless Linux without WSL). WSL is excluded from the headless gate because browser opening works via Windows interop. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
121
src/setup/platform.test.ts
Normal file
121
src/setup/platform.test.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
import {
|
||||
getPlatform,
|
||||
isWSL,
|
||||
isRoot,
|
||||
isHeadless,
|
||||
hasSystemd,
|
||||
getServiceManager,
|
||||
commandExists,
|
||||
getNodeVersion,
|
||||
getNodeMajorVersion,
|
||||
} from './platform.js';
|
||||
|
||||
// --- getPlatform ---
|
||||
|
||||
describe('getPlatform', () => {
|
||||
it('returns a valid platform string', () => {
|
||||
const result = getPlatform();
|
||||
expect(['macos', 'linux', 'unknown']).toContain(result);
|
||||
});
|
||||
|
||||
it('returns linux on this system', () => {
|
||||
// This test runs on Linux
|
||||
expect(getPlatform()).toBe('linux');
|
||||
});
|
||||
});
|
||||
|
||||
// --- isWSL ---
|
||||
|
||||
describe('isWSL', () => {
|
||||
it('returns a boolean', () => {
|
||||
expect(typeof isWSL()).toBe('boolean');
|
||||
});
|
||||
|
||||
it('checks /proc/version for WSL markers', () => {
|
||||
// On non-WSL Linux, should return false
|
||||
// On WSL, should return true
|
||||
// Just verify it doesn't throw
|
||||
const result = isWSL();
|
||||
expect(typeof result).toBe('boolean');
|
||||
});
|
||||
});
|
||||
|
||||
// --- isRoot ---
|
||||
|
||||
describe('isRoot', () => {
|
||||
it('returns a boolean', () => {
|
||||
expect(typeof isRoot()).toBe('boolean');
|
||||
});
|
||||
});
|
||||
|
||||
// --- isHeadless ---
|
||||
|
||||
describe('isHeadless', () => {
|
||||
it('returns a boolean', () => {
|
||||
expect(typeof isHeadless()).toBe('boolean');
|
||||
});
|
||||
});
|
||||
|
||||
// --- hasSystemd ---
|
||||
|
||||
describe('hasSystemd', () => {
|
||||
it('returns a boolean', () => {
|
||||
expect(typeof hasSystemd()).toBe('boolean');
|
||||
});
|
||||
|
||||
it('checks /proc/1/comm', () => {
|
||||
// On systemd systems, should return true
|
||||
// Just verify it doesn't throw
|
||||
const result = hasSystemd();
|
||||
expect(typeof result).toBe('boolean');
|
||||
});
|
||||
});
|
||||
|
||||
// --- getServiceManager ---
|
||||
|
||||
describe('getServiceManager', () => {
|
||||
it('returns a valid service manager', () => {
|
||||
const result = getServiceManager();
|
||||
expect(['launchd', 'systemd', 'none']).toContain(result);
|
||||
});
|
||||
|
||||
it('returns systemd or none on Linux', () => {
|
||||
const result = getServiceManager();
|
||||
// On Linux, should be systemd if available, else none
|
||||
expect(['systemd', 'none']).toContain(result);
|
||||
});
|
||||
});
|
||||
|
||||
// --- commandExists ---
|
||||
|
||||
describe('commandExists', () => {
|
||||
it('returns true for node', () => {
|
||||
expect(commandExists('node')).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for nonexistent command', () => {
|
||||
expect(commandExists('this_command_does_not_exist_xyz_123')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// --- getNodeVersion ---
|
||||
|
||||
describe('getNodeVersion', () => {
|
||||
it('returns a version string', () => {
|
||||
const version = getNodeVersion();
|
||||
expect(version).not.toBeNull();
|
||||
expect(version).toMatch(/^\d+\.\d+\.\d+/);
|
||||
});
|
||||
});
|
||||
|
||||
// --- getNodeMajorVersion ---
|
||||
|
||||
describe('getNodeMajorVersion', () => {
|
||||
it('returns at least 20', () => {
|
||||
const major = getNodeMajorVersion();
|
||||
expect(major).not.toBeNull();
|
||||
expect(major!).toBeGreaterThanOrEqual(20);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user