release: v0.16.0

See CHANGELOG.md for the full v0.16.0 entry.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Ralf <rohlenbo79@gmail.com>
Co-authored-by: Paul Thies <110185070+ImScheinox@users.noreply.github.com>
Co-authored-by: lancetm714 <108419425+lancetm714@users.noreply.github.com>
This commit is contained in:
Ralksta
2026-09-22 14:12:13 +02:00
committed by GitHub
parent c3f92ca4ec
commit a3a6319c29
120 changed files with 6818 additions and 975 deletions

View File

@@ -14,12 +14,14 @@ import { mkdtemp, mkdir, readdir, readFile, rm, writeFile } from 'fs/promises';
let root: string;
let journalDir: string;
let backupDir: string;
let essaysDir: string;
let service: typeof import('../admin/journal-service');
beforeEach(async () => {
root = await mkdtemp(path.join(os.tmpdir(), 'folio-journal-'));
journalDir = path.join(root, 'content', 'journal');
backupDir = path.join(journalDir, '.backups');
essaysDir = path.join(root, 'content', 'essays');
await mkdir(journalDir, { recursive: true });
vi.spyOn(process, 'cwd').mockReturnValue(root);
vi.spyOn(console, 'log').mockImplementation(() => {});
@@ -62,6 +64,56 @@ describe('deleteJournalEntry', () => {
});
});
describe('writeJournalEntry backup failure (#630)', () => {
it('still saves a brand-new entry with no backup', async () => {
await expect(service.writeJournalEntry('trip', entry('New'))).resolves.toBeUndefined();
await expect(readdir(backupDir).catch(() => [])).resolves.toEqual([]);
});
it('aborts the save when the entry exists but the backup cannot be written', async () => {
await writeFile(path.join(journalDir, 'trip.md'), entry('Old'));
// A file where the backup directory should be makes mkdir and copy fail.
await writeFile(backupDir, 'not a directory');
await expect(service.writeJournalEntry('trip', entry('New'))).rejects.toThrow();
await expect(readFile(path.join(journalDir, 'trip.md'), 'utf8')).resolves.toBe(entry('Old'));
});
});
describe('legacy content/essays/ entries (#631)', () => {
it('retires the legacy copy once a legacy entry is edited', async () => {
await mkdir(essaysDir, { recursive: true });
await writeFile(path.join(essaysDir, 'trip.md'), entry('Old'));
await service.writeJournalEntry('trip', entry('New'));
await expect(readdir(essaysDir)).resolves.not.toContain('trip.md');
await expect(readFile(path.join(journalDir, 'trip.md'), 'utf8')).resolves.toBe(entry('New'));
});
it('does not resurrect a legacy entry after it is edited then deleted', async () => {
await mkdir(essaysDir, { recursive: true });
await writeFile(path.join(essaysDir, 'trip.md'), entry('Old'));
await service.writeJournalEntry('trip', entry('New'));
await expect(service.deleteJournalEntry('trip')).resolves.toBe(true);
const entries = await service.listJournalEntries();
expect(entries.map((e) => e.slug)).not.toContain('trip');
});
it('deletes a legacy entry that was never edited, from content/essays/', async () => {
await mkdir(essaysDir, { recursive: true });
await writeFile(path.join(essaysDir, 'trip.md'), entry('Legacy'));
await expect(service.deleteJournalEntry('trip')).resolves.toBe(true);
await expect(readdir(essaysDir)).resolves.not.toContain('trip.md');
const entries = await service.listJournalEntries();
expect(entries.map((e) => e.slug)).not.toContain('trip');
});
});
describe('restoreJournalBackup', () => {
it('brings a deleted entry back under its slug', async () => {
await writeFile(path.join(journalDir, 'trip.md'), entry('Trip'));