From 6d6899a098528b73d67df1e53740866079ce6534 Mon Sep 17 00:00:00 2001 From: Will Pike <6687499+pike00@users.noreply.github.com> Date: Thu, 30 Jul 2026 22:58:02 -0500 Subject: [PATCH] feat: add support for config-based excludePrefixes This patch introduces the `excludePrefixes` optional property to `PeerStorageConf` and implements `isExcludedPath()` in `PeerStorage`. The functionality is integrated into `put`, `delete`, `dispatch`, `dispatchDeleted`, and the offline scanning loop so that matching paths are bypassed. A test has been added to `tests/Patch5.test.ts` to verify the logic. --- PeerStorage.ts | 20 ++++++++++++++++++++ tests/Patch5.test.ts | 32 ++++++++++++++++++++++++++++++++ types.ts | 1 + 3 files changed, 53 insertions(+) create mode 100644 tests/Patch5.test.ts diff --git a/PeerStorage.ts b/PeerStorage.ts index 9dbd088..73501bb 100644 --- a/PeerStorage.ts +++ b/PeerStorage.ts @@ -20,8 +20,20 @@ export class PeerStorage extends Peer { super(conf, dispatcher); } + isExcludedPath(path: string): boolean { + if (!this.config.excludePrefixes || this.config.excludePrefixes.length === 0) { + return false; + } + // Exclude prefixes typically match the beginning of the file path. + // Let's normalize it to be sure we're matching string prefixes properly. + // The path being checked might be a posix path like "some_folder/file.txt". + return this.config.excludePrefixes.some(prefix => path.startsWith(prefix)); + } + async delete(pathSrc: string): Promise { const lp = this.toLocalPath(pathSrc); + const relativePath = this.toPosixPath(pathSrc); + if (this.isExcludedPath(relativePath)) return false; const path = this.toStoragePath(lp); if (await this.isRepeating(lp, false)) { return false; @@ -39,6 +51,8 @@ export class PeerStorage extends Peer { } async put(pathSrc: string, data: FileData): Promise { const lp = this.toLocalPath(pathSrc); + const relativePath = this.toPosixPath(pathSrc); + if (this.isExcludedPath(relativePath)) return false; const path = this.toStoragePath(lp); if (await this.isRepeating(lp, data)) { this.receiveLog(`${lp} save repeating`); @@ -157,6 +171,8 @@ export class PeerStorage extends Peer { const lP = this.toStoragePath(this.toLocalPath(".")); const path = this.toPosixPath(relative(lP, pathSrc)); + if (this.isExcludedPath(path)) return; + const data = await this.get(path); if (data === false) return; @@ -177,6 +193,9 @@ export class PeerStorage extends Peer { async dispatchDeleted(pathSrc: string) { const lP = this.toStoragePath(this.toLocalPath(".")); const path = this.toPosixPath(relative(lP, pathSrc)); + + if (this.isExcludedPath(path)) return; + await scheduleOnceIfDuplicated(pathSrc, async () => { await delay(250); if (!await this.isRepeating(path, false)) { @@ -260,6 +279,7 @@ export class PeerStorage extends Peer { for await (const entry of walk(lP)) { if (entry.isFile) { const ePath = this.toPosixPath(relative(this.toLocalPath("."), entry.path)); + if (this.isExcludedPath(ePath)) continue; if (await this.isChanged(ePath)) { this.debugLog(`Offline changes detected: ${ePath}`); await this.dispatch(entry.path); diff --git a/tests/Patch5.test.ts b/tests/Patch5.test.ts new file mode 100644 index 0000000..a00e692 --- /dev/null +++ b/tests/Patch5.test.ts @@ -0,0 +1,32 @@ +import { assertEquals } from "jsr:@std/assert"; +import { PeerStorage } from "../PeerStorage.ts"; +import { PeerStorageConf } from "../types.ts"; + +Deno.test("PeerStorage - isExcludedPath", () => { + const config: PeerStorageConf = { + type: "storage", + name: "test-storage", + baseDir: "./test-dir", + excludePrefixes: [".git/", "node_modules/", "tmp_"] + }; + + // A dummy dispatcher function + const dummyDispatcher = async () => {}; + + const peer = new PeerStorage(config, dummyDispatcher); + + assertEquals(peer.isExcludedPath(".git/config"), true); + assertEquals(peer.isExcludedPath("node_modules/package.json"), true); + assertEquals(peer.isExcludedPath("tmp_file.txt"), true); + assertEquals(peer.isExcludedPath("src/main.ts"), false); + assertEquals(peer.isExcludedPath("test-dir/.git/config"), false); // Exclude prefixes usually apply after baseDir resolution in dispatch, so the relative path is checked. + + // Test with no excludePrefixes configured + const configNoExcludes: PeerStorageConf = { + type: "storage", + name: "test-storage-2", + baseDir: "./test-dir" + }; + const peerNoExcludes = new PeerStorage(configNoExcludes, dummyDispatcher); + assertEquals(peerNoExcludes.isExcludedPath(".git/config"), false); +}); diff --git a/types.ts b/types.ts index 12c79c0..a2d6dff 100644 --- a/types.ts +++ b/types.ts @@ -16,6 +16,7 @@ export interface PeerStorageConf { args: string[] } useChokidar?: boolean; + excludePrefixes?: string[]; } export interface PeerCouchDBConf extends DirectFileManipulatorOptions { /** Glob patterns for i:-prefixed (internal) files to sync, e.g. [".claude/**"] */