Replace bash mosaic-init with a modern 9-stage wizard: - SOUL.md identity, USER.md profile, TOOLS.md configuration - Runtime detection (Claude, Codex, OpenCode) + MCP setup - Skills catalog with categorized selection - Quick Start and Advanced modes - HeadlessPrompter for --non-interactive and CI usage - ConfigService abstraction layer for future DB migration - Bundled as single dist/mosaic-wizard.mjs via tsdown - mosaic init now prefers wizard when Node.js is available - 30 tests covering stages, templates, and integration Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
72 lines
2.1 KiB
TypeScript
72 lines
2.1 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { tmpdir } from 'node:os';
|
|
import { HeadlessPrompter } from '../../src/prompter/headless-prompter.js';
|
|
import { detectInstallStage } from '../../src/stages/detect-install.js';
|
|
import type { WizardState } from '../../src/types.js';
|
|
import type { ConfigService } from '../../src/config/config-service.js';
|
|
|
|
function createState(mosaicHome: string): WizardState {
|
|
return {
|
|
mosaicHome,
|
|
sourceDir: mosaicHome,
|
|
mode: 'quick',
|
|
installAction: 'fresh',
|
|
soul: {},
|
|
user: {},
|
|
tools: {},
|
|
runtimes: { detected: [], mcpConfigured: false },
|
|
selectedSkills: [],
|
|
};
|
|
}
|
|
|
|
const mockConfig: ConfigService = {
|
|
readSoul: async () => ({ agentName: 'TestAgent' }),
|
|
readUser: async () => ({ userName: 'TestUser' }),
|
|
readTools: async () => ({}),
|
|
writeSoul: async () => {},
|
|
writeUser: async () => {},
|
|
writeTools: async () => {},
|
|
syncFramework: async () => {},
|
|
};
|
|
|
|
describe('detectInstallStage', () => {
|
|
let tmpDir: string;
|
|
|
|
beforeEach(() => {
|
|
tmpDir = mkdtempSync(join(tmpdir(), 'mosaic-test-'));
|
|
});
|
|
|
|
afterEach(() => {
|
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
});
|
|
|
|
it('sets fresh for empty directory', async () => {
|
|
const p = new HeadlessPrompter({});
|
|
const state = createState(join(tmpDir, 'nonexistent'));
|
|
await detectInstallStage(p, state, mockConfig);
|
|
|
|
expect(state.installAction).toBe('fresh');
|
|
});
|
|
|
|
it('detects existing install and offers choices', async () => {
|
|
// Create a mock existing install
|
|
mkdirSync(join(tmpDir, 'bin'), { recursive: true });
|
|
writeFileSync(join(tmpDir, 'AGENTS.md'), '# Test');
|
|
writeFileSync(
|
|
join(tmpDir, 'SOUL.md'),
|
|
'You are **Jarvis** in this session.',
|
|
);
|
|
|
|
const p = new HeadlessPrompter({
|
|
'What would you like to do?': 'keep',
|
|
});
|
|
const state = createState(tmpDir);
|
|
await detectInstallStage(p, state, mockConfig);
|
|
|
|
expect(state.installAction).toBe('keep');
|
|
expect(state.soul.agentName).toBe('TestAgent');
|
|
});
|
|
});
|