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

@@ -61,17 +61,22 @@ describe('restoreBackup rejects anything that is not a backup it produced', () =
describe('restoreBackup accepts the names it writes', () => {
beforeEach(() => vi.clearAllMocks());
// The restore itself goes through atomicWrite (writeFile the temp file,
// then rename it onto the target) rather than fs.copyFile, so a failure
// partway through cannot truncate the live file (#630). fs.copyFile is
// still used, but only for the pre-restore safety snapshot.
it('restores a normal backup', async () => {
await expect(
restoreBackup('gallery.yaml.2026-05-31T17-30-00-000Z.bak'),
).resolves.toBeUndefined();
expect(fs.copyFile).toHaveBeenCalled();
expect(fs.copyFile).toHaveBeenCalled(); // pre-restore snapshot
expect(fs.rename).toHaveBeenCalled(); // atomic restore write
});
it('restores an about.md backup onto content/about.md', async () => {
await restoreBackup('about.md.2026-05-31T17-30-00-000Z.bak');
const calls = vi.mocked(fs.copyFile).mock.calls;
const calls = vi.mocked(fs.rename).mock.calls;
expect(String(calls[calls.length - 1][1])).toMatch(/content\/about\.md$/);
});
@@ -84,8 +89,38 @@ describe('restoreBackup accepts the names it writes', () => {
it('derives the destination from the matched name, not a substring search', async () => {
await restoreBackup('settings.yaml.2026-05-31T17-30-00-000Z.bak');
// Last copyFile is backup → target; the first is the pre-restore snapshot.
const calls = vi.mocked(fs.copyFile).mock.calls;
const calls = vi.mocked(fs.rename).mock.calls;
expect(String(calls[calls.length - 1][1])).toMatch(/content\/settings\.yaml$/);
});
it('reads the backup before touching the pre-restore snapshot or the live file', async () => {
await restoreBackup('gallery.yaml.2026-05-31T17-30-00-000Z.bak');
const readOrder = vi.mocked(fs.readFile).mock.invocationCallOrder[0];
const copyOrder = vi.mocked(fs.copyFile).mock.invocationCallOrder[0];
expect(readOrder).toBeLessThan(copyOrder);
});
it('aborts without touching the live file when the safety snapshot fails for a reason other than ENOENT', async () => {
vi.mocked(fs.copyFile).mockRejectedValueOnce(
Object.assign(new Error('EACCES'), { code: 'EACCES' }),
);
await expect(restoreBackup('gallery.yaml.2026-05-31T17-30-00-000Z.bak')).rejects.toThrow(
'EACCES',
);
expect(fs.writeFile).not.toHaveBeenCalled();
expect(fs.rename).not.toHaveBeenCalled();
});
it('proceeds when the safety snapshot fails only because there is no current file yet', async () => {
vi.mocked(fs.copyFile).mockRejectedValueOnce(
Object.assign(new Error('ENOENT'), { code: 'ENOENT' }),
);
await expect(
restoreBackup('gallery.yaml.2026-05-31T17-30-00-000Z.bak'),
).resolves.toBeUndefined();
expect(fs.rename).toHaveBeenCalled();
});
});