chore: consolidate new foundation and archive v1 (#1495)
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
|
||||
// homedir/platform are read at call time, so they can be stubbed per case.
|
||||
vi.mock('node:os', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('node:os')>();
|
||||
return {
|
||||
...actual,
|
||||
homedir: () => '/home/tester',
|
||||
platform: () => mockPlatform,
|
||||
};
|
||||
});
|
||||
|
||||
let mockPlatform: NodeJS.Platform = 'linux';
|
||||
|
||||
const { getShellProfilePath, detectShell } = await import('../../src/platform/detect.js');
|
||||
|
||||
describe('getShellProfilePath', () => {
|
||||
const originalShell = process.env['SHELL'];
|
||||
const originalZdotdir = process.env['ZDOTDIR'];
|
||||
|
||||
beforeEach(() => {
|
||||
mockPlatform = 'linux';
|
||||
delete process.env['ZDOTDIR'];
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (originalShell === undefined) delete process.env['SHELL'];
|
||||
else process.env['SHELL'] = originalShell;
|
||||
if (originalZdotdir === undefined) delete process.env['ZDOTDIR'];
|
||||
else process.env['ZDOTDIR'] = originalZdotdir;
|
||||
});
|
||||
|
||||
// The regression this guards: setupPath() in stages/finalize.ts appends the
|
||||
// PATH export to whatever this returns. A line written to ~/.bashrc is
|
||||
// unreachable to `bash -lc`, systemd units and agent seats, because Debian's
|
||||
// default .bashrc returns early for non-interactive shells — so an install
|
||||
// reported success and left `mosaic: command not found`. Same for .zshrc,
|
||||
// which zsh only reads for interactive shells.
|
||||
it('never targets an interactive-only rc file', () => {
|
||||
for (const shell of ['/bin/bash', '/usr/bin/zsh']) {
|
||||
process.env['SHELL'] = shell;
|
||||
const profile = getShellProfilePath();
|
||||
expect(profile).not.toMatch(/\.bashrc$/);
|
||||
expect(profile).not.toMatch(/\.zshrc$/);
|
||||
}
|
||||
});
|
||||
|
||||
it('uses ~/.profile for bash', () => {
|
||||
process.env['SHELL'] = '/bin/bash';
|
||||
expect(getShellProfilePath()).toBe('/home/tester/.profile');
|
||||
});
|
||||
|
||||
it('uses ~/.zshenv for zsh', () => {
|
||||
process.env['SHELL'] = '/usr/bin/zsh';
|
||||
expect(getShellProfilePath()).toBe('/home/tester/.zshenv');
|
||||
});
|
||||
|
||||
it('honours ZDOTDIR for zsh', () => {
|
||||
process.env['SHELL'] = '/usr/bin/zsh';
|
||||
process.env['ZDOTDIR'] = '/custom/zdot';
|
||||
expect(getShellProfilePath()).toBe('/custom/zdot/.zshenv');
|
||||
});
|
||||
|
||||
it('falls back to ~/.profile for an unknown shell', () => {
|
||||
process.env['SHELL'] = '/bin/somethingelse';
|
||||
expect(detectShell()).toBe('unknown');
|
||||
expect(getShellProfilePath()).toBe('/home/tester/.profile');
|
||||
});
|
||||
|
||||
it('still routes fish to its own config', () => {
|
||||
process.env['SHELL'] = '/usr/bin/fish';
|
||||
expect(getShellProfilePath()).toBe('/home/tester/.config/fish/config.fish');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
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');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user