Files
nanoclaw/src/setup/platform.test.ts
Daniel M 8fc1c23925 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>
2026-02-22 18:25:11 +02:00

122 lines
2.7 KiB
TypeScript

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);
});
});