Skip to content

Commit 1e72fcd

Browse files
committed
feat: allow deletion and creation of files with patches
1 parent ff4546d commit 1e72fcd

File tree

5 files changed

+30
-7
lines changed

5 files changed

+30
-7
lines changed

api/src/unraid-api/unraid-file-modifier/file-modification.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,12 @@ export abstract class FileModification {
9292
if (!patchContents.trim()) {
9393
throw new Error('Patch contents are empty');
9494
}
95-
const currentContent = await readFile(this.filePath, 'utf8');
95+
const currentContent = await readFile(this.filePath, 'utf8').catch(() => '');
9696
const parsedPatch = parsePatch(patchContents)[0];
9797
if (!parsedPatch?.hunks.length) {
9898
throw new Error('Invalid Patch Format: No hunks found');
9999
}
100+
100101
const results = applyPatch(currentContent, parsedPatch);
101102
if (results === false) {
102103
throw new Error(`Failed to apply patch to ${this.filePath}`);
@@ -178,7 +179,12 @@ export abstract class FileModification {
178179
throw new Error(`Failed to rollback patch from ${this.filePath}`);
179180
}
180181

181-
await writeFile(this.filePath, results);
182+
if (results === '') {
183+
// Delete the file if the patch results in an empty string
184+
await unlink(this.filePath);
185+
} else {
186+
await writeFile(this.filePath, results);
187+
}
182188

183189
// Clean up the patch file after successful rollback
184190
try {

api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/logrotate.conf

Whitespace-only changes.

api/src/unraid-api/unraid-file-modifier/modifications/__test__/generic-modification.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ const downloadOrRetrieveOriginalFile = async (filePath: string, fileUrl: string)
7474
);
7575
}
7676
}
77-
return await readFile(filePath, 'utf-8');
77+
return await readFile(filePath, 'utf-8').catch(() => '');
7878
};
7979

8080
async function testModification(testCase: ModificationTestCase, patcher: FileModification) {
@@ -101,7 +101,7 @@ async function testModification(testCase: ModificationTestCase, patcher: FileMod
101101

102102
// Rollback and verify original state
103103
await patcher.rollback();
104-
const revertedContent = await readFile(filePath, 'utf-8');
104+
const revertedContent = await readFile(filePath, 'utf-8').catch(() => '');
105105
await expect(revertedContent).toMatch(originalContent);
106106
}
107107

api/src/unraid-api/unraid-file-modifier/modifications/log-rotate.modification.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import { Logger } from '@nestjs/common';
22
import { readFile } from 'node:fs/promises';
33

4-
import { createPatch } from 'diff';
5-
import { execa } from 'execa';
6-
74
import { fileExists } from '@app/core/utils/files/file-exists';
85
import {
96
FileModification,

api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.spec.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
ShouldApplyWithReason,
1212
} from '@app/unraid-api/unraid-file-modifier/file-modification';
1313
import { UnraidFileModificationService } from '@app/unraid-api/unraid-file-modifier/unraid-file-modifier.service';
14+
import { fileExistsSync } from '@app/core/utils/files/file-exists';
1415

1516
const FIXTURE_PATH = join(__dirname, 'modifications', '__test__', '__fixtures__', 'text-patch-file.txt');
1617
const ORIGINAL_CONTENT = 'original';
@@ -80,6 +81,25 @@ describe.sequential('FileModificationService', () => {
8081
await expect(service.applyModification(mod)).resolves.toBe(undefined);
8182
});
8283

84+
it('should apply modification if file does not exist', async () => {
85+
const mod = new TestFileModification(logger);
86+
// @ts-expect-error - This is a protected method, but we need to mock it
87+
mod.generatePatch = vi.fn().mockResolvedValue(createPatch(FIXTURE_PATH, '', 'modified'));
88+
await fs.unlink(FIXTURE_PATH);
89+
await expect(service.applyModification(mod)).resolves.toBe(undefined);
90+
expect(mockLogger.warn).toHaveBeenCalledWith('Could not load pregenerated patch for: test');
91+
expect(mockLogger.log).toHaveBeenCalledWith(
92+
'Applying modification: test - Always Apply this mod'
93+
);
94+
expect(mockLogger.log).toHaveBeenCalledWith('Modification applied successfully: test');
95+
const content = await fs.readFile(FIXTURE_PATH, 'utf-8');
96+
expect(content).toBe('modified');
97+
await service.rollbackAll();
98+
expect(fileExistsSync(FIXTURE_PATH)).toBe(false);
99+
expect(mockLogger.log).toHaveBeenCalledWith('Rolling back modification: test');
100+
expect(mockLogger.log).toHaveBeenCalledWith('Successfully rolled back modification: test');
101+
});
102+
83103
it('should not rollback any mods without loaded', async () => {
84104
await expect(service.rollbackAll()).resolves.toBe(undefined);
85105
});

0 commit comments

Comments
 (0)