import { test, describe, after } from 'node:test'; import assert from 'node:assert/strict'; import { existsSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { execFileSync } from 'node:child_process'; import { fileURLToPath } from 'node:url'; import { tempRepo, cleanup, TEST_ENV } from './helpers.mjs'; after(cleanup); const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..'); const ONBOARD = join(ROOT, 'scripts', 'onboard.mjs'); /** Run the onboarding script directly. Never throws — returns {code, stdout, stderr}. */ function onboard(args) { try { const stdout = execFileSync(process.execPath, [ONBOARD, ...args], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], env: TEST_ENV, }); return { code: 0, stdout, stderr: '' }; } catch (e) { return { code: e.status ?? 1, stdout: e.stdout ?? '', stderr: e.stderr ?? '' }; } } describe('onboard script', () => { test('--dry-run writes nothing', () => { const dir = tempRepo(); const r = onboard([dir, '--dry-run']); assert.equal(r.code, 0); assert.ok(!existsSync(join(dir, '.github', 'agents')), 'dry run must not create files'); assert.ok(!existsSync(join(dir, '.agents')), 'dry run must not run lh init'); assert.match(r.stdout, /would write/); }); test('--yes --no-npm-link copies the behaviour layer and runs lh init + doctor', () => { const dir = tempRepo({ 'package.json': JSON.stringify({ name: 'fixture', version: '1.0.0', scripts: { test: 'true' } }, null, 2), }); const r = onboard([dir, '--yes', '--no-npm-link']); assert.equal(r.code, 0, r.stdout + r.stderr); for (const rel of ['.github/agents/conductor.agent.md', '.github/skills/design/SKILL.md', '.github/mcp.json', 'AGENTS.md', '.agents/harness.config.json']) { assert.ok(existsSync(join(dir, rel)), `expected ${rel} to exist`); } assert.match(r.stdout, /doctor: all required checks passed/); }); test('rerunning with --yes is idempotent (no forced overwrite, no init failure)', () => { const dir = tempRepo({ 'package.json': JSON.stringify({ name: 'fixture', version: '1.0.0', scripts: { test: 'true' } }, null, 2), }); assert.equal(onboard([dir, '--yes', '--no-npm-link']).code, 0); const second = onboard([dir, '--yes', '--no-npm-link']); assert.equal(second.code, 0, second.stdout + second.stderr); assert.match(second.stdout, /already up to date/); assert.match(second.stdout, /already initialized/); }); test('refuses a target directory that does not exist', () => { const r = onboard(['/nonexistent/path/for/sure', '--yes']); assert.notEqual(r.code, 0); }); test('a conflicting existing file is skipped unless --force', () => { const dir = tempRepo({ 'AGENTS.md': 'custom project notes, do not clobber\n' }); const r = onboard([dir, '--yes', '--no-npm-link']); assert.equal(r.code, 0); assert.match(r.stdout, /skipped/); const kept = existsSync(join(dir, 'AGENTS.md')); assert.ok(kept, 'AGENTS.md must still exist'); const forced = onboard([dir, '--yes', '--no-npm-link', '--force']); assert.equal(forced.code, 0); assert.match(forced.stdout, /written/); }); });