import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, existsSync, chmodSync, rmSync, } from 'node:fs'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import { syncDirectory } from '../../src/platform/file-ops.js'; describe('syncDirectory', () => { let tmpDir: string; beforeEach(() => { tmpDir = mkdtempSync(join(tmpdir(), 'mosaic-file-ops-')); }); afterEach(() => { rmSync(tmpDir, { recursive: true, force: true }); }); it('is a no-op when source and target are the same path', () => { const dir = join(tmpDir, 'same'); mkdirSync(dir, { recursive: true }); writeFileSync(join(dir, 'file.txt'), 'hello'); // Should not throw even with read-only files const gitDir = join(dir, '.git', 'objects', 'pack'); mkdirSync(gitDir, { recursive: true }); const packFile = join(gitDir, 'pack-abc.idx'); writeFileSync(packFile, 'data'); chmodSync(packFile, 0o444); expect(() => syncDirectory(dir, dir)).not.toThrow(); }); it('skips nested .git directories when excludeGit is true', () => { const src = join(tmpDir, 'src'); const dest = join(tmpDir, 'dest'); // Create source with a nested .git mkdirSync(join(src, 'sources', 'skills', '.git', 'objects'), { recursive: true }); writeFileSync(join(src, 'sources', 'skills', '.git', 'objects', 'pack.idx'), 'git-data'); writeFileSync(join(src, 'sources', 'skills', 'SKILL.md'), 'skill content'); writeFileSync(join(src, 'README.md'), 'readme'); syncDirectory(src, dest, { excludeGit: true }); // .git contents should NOT be copied expect(existsSync(join(dest, 'sources', 'skills', '.git'))).toBe(false); // Normal files should be copied expect(readFileSync(join(dest, 'sources', 'skills', 'SKILL.md'), 'utf-8')).toBe( 'skill content', ); expect(readFileSync(join(dest, 'README.md'), 'utf-8')).toBe('readme'); }); it('copies nested .git directories when excludeGit is false', () => { const src = join(tmpDir, 'src'); const dest = join(tmpDir, 'dest'); mkdirSync(join(src, 'sub', '.git'), { recursive: true }); writeFileSync(join(src, 'sub', '.git', 'HEAD'), 'ref: refs/heads/main'); syncDirectory(src, dest, { excludeGit: false }); expect(readFileSync(join(dest, 'sub', '.git', 'HEAD'), 'utf-8')).toBe('ref: refs/heads/main'); }); it('respects preserve option', () => { const src = join(tmpDir, 'src'); const dest = join(tmpDir, 'dest'); mkdirSync(src, { recursive: true }); mkdirSync(dest, { recursive: true }); writeFileSync(join(src, 'SOUL.md'), 'new soul'); writeFileSync(join(dest, 'SOUL.md'), 'old soul'); writeFileSync(join(src, 'README.md'), 'new readme'); syncDirectory(src, dest, { preserve: ['SOUL.md'] }); expect(readFileSync(join(dest, 'SOUL.md'), 'utf-8')).toBe('old soul'); expect(readFileSync(join(dest, 'README.md'), 'utf-8')).toBe('new readme'); }); });