From 80bb8b896d2819fb6c81218a907b6cf935650a0c Mon Sep 17 00:00:00 2001 From: "nathan.bougie" Date: Wed, 17 Sep 2025 19:42:49 -0400 Subject: [PATCH 1/5] fix: Changed branch name --- .github/workflows/ci-cd.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 7f698f7..5c3ce24 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -3,10 +3,7 @@ name: CI/CD Pipeline on: push: branches: - - production-release - pull_request: - branches: - - production-release + - main jobs: build: From ad97f278a1d6d02d4556692ebd645fe6471fd980 Mon Sep 17 00:00:00 2001 From: "nathan.bougie" Date: Wed, 17 Sep 2025 19:43:41 -0400 Subject: [PATCH 2/5] fix: changed publisher name --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7636a76..2dfb7fe 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Prompt Vault extension", "description": "Sync GitHub Copilot prompts, chatmodes and instructions from git repositories", "version": "1.2.0", - "publisher": "logient-nventive", + "publisher": "logientnventive", "repository": { "type": "git", "url": "https://github.com/nventive/PromptVault.git", From 1ab7711dbd68bb1ba9d6355a5cbd50f1cae74abc Mon Sep 17 00:00:00 2001 From: "nathan.bougie" Date: Wed, 17 Sep 2025 20:28:05 -0400 Subject: [PATCH 3/5] feat: Fetch file hash before fetching entire file --- src/syncManager.ts | 26 ++++++++++++++++++++------ src/utils/fileSystem.ts | 15 +++++++++++++++ src/utils/github.ts | 22 ++++++++++++++++++++++ 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/src/syncManager.ts b/src/syncManager.ts index 07795b9..015dd7d 100644 --- a/src/syncManager.ts +++ b/src/syncManager.ts @@ -171,26 +171,39 @@ export class SyncManager { await this.fileSystem.ensureDirectoryExists(promptsDir); let itemsUpdated = 0; + let statusBarRepoCount = 0; for (const file of files) { this.logger.debug(`Syncing file: ${file.path}`); + this.statusBar.setStatus(SyncStatus.Syncing, `Syncing ${statusBarRepoCount}/${files.length}`); + statusBarRepoCount++; + let content = null; + const fileName = this.fileSystem.getBasename(file.path); + const localPath = this.fileSystem.joinPath(promptsDir, fileName); + + const fileExists = await this.fileSystem.fileExists(localPath); + if(fileExists) { + const localFileContent = await this.fileSystem.readFileContent(localPath); + const localFileHash = await this.fileSystem.calculateFileHash(localFileContent); + + const githubFileHash = await this.github.getFileHash(owner, repo, file.path, this.config.branch); + if(localFileHash === githubFileHash) { + this.logger.info(`File ${file.path} is up to date, skipping`); + continue; + } + } try { content = await this.github.getFileContent(owner, repo, file.path, this.config.branch); } catch (error) { // An error occured will retrieving file content, Return here this.logger.warn(`Failed to fetch content for ${file.path}: ${error}`); - this.notifications.showSyncError(`Failed to fetch content for ${file.path}: ${error}. Make sure that the correct is set. Current branch: ${this.config.branch}`); + this.notifications.showSyncError(`Failed to fetch content for ${file.path}: ${error}. Make sure that the branch is correct. Current branch: ${this.config.branch}`); return itemsUpdated; } try { - // Flatten the structure - extract just the filename and place directly in prompts directory - const fileName = this.fileSystem.getBasename(file.path); - const localPath = this.fileSystem.joinPath(promptsDir, fileName); - - // Check if file needs updating if(!content) { this.logger.warn(`No content retrieved for ${file.path}, skipping`); continue; @@ -205,6 +218,7 @@ export class SyncManager { this.logger.debug(`File unchanged: ${localPath}`); } + } catch (error) { this.logger.warn(`Failed to sync file ${file.path}: ${error}`); // Continue with other files even if one fails diff --git a/src/utils/fileSystem.ts b/src/utils/fileSystem.ts index 29808f5..4085cc1 100644 --- a/src/utils/fileSystem.ts +++ b/src/utils/fileSystem.ts @@ -61,6 +61,21 @@ export class FileSystemManager { async copyFile(source: string, destination: string): Promise { const content = await this.readFileContent(source); await this.writeFileContent(destination, content); + } + + async calculateFileHash(content: string): Promise { + // GitHub uses the following format: `blob ${content.length}\0${content}` + const blobPrefix = `blob ${Buffer.from(content).length}\0`; + const fullContent = blobPrefix + content; + + const encoder = new TextEncoder(); + const data = encoder.encode(fullContent); + const hashBuffer = await crypto.subtle.digest('SHA-1', data); + + const hashArray = Array.from(new Uint8Array(hashBuffer)); + const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); + + return hashHex; } getRelativePath(from: string, to: string): string { diff --git a/src/utils/github.ts b/src/utils/github.ts index 0591af1..6367586 100644 --- a/src/utils/github.ts +++ b/src/utils/github.ts @@ -111,6 +111,28 @@ export class GitHubApiManager { throw new Error('No content found in file'); } + async getFileHash(owner: string, repo: string, path: string, branch: string = 'master'): Promise { + const session = await vscode.authentication.getSession('github', ['repo'], { createIfNone: false }); + if (!session) { + throw new Error('GitHub authentication required'); + } + + const response = await fetch(`${this.baseUrl}/repos/${owner}/${repo}/contents/${path}?ref=${branch}`, { + headers: { + 'Authorization': `Bearer ${session.accessToken}`, + 'Accept': 'application/vnd.github.v3+json', + 'User-Agent': 'VS Code Prompts Sync Extension' + } + }); + + if (!response.ok) { + throw new Error(`Failed to get file hash: ${response.status} ${response.statusText}`); + } + + const data = await response.json(); + return data.sha; + } + parseRepositoryUrl(url: string): { owner: string; repo: string } { // Handle different GitHub URL formats const patterns = [ From 1f305baa9cd1a5a8bc6b9f79202584ab26bc7bc5 Mon Sep 17 00:00:00 2001 From: "nathan.bougie" Date: Wed, 17 Sep 2025 20:35:10 -0400 Subject: [PATCH 4/5] Removed old code --- src/syncManager.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/syncManager.ts b/src/syncManager.ts index 015dd7d..1300390 100644 --- a/src/syncManager.ts +++ b/src/syncManager.ts @@ -208,17 +208,9 @@ export class SyncManager { this.logger.warn(`No content retrieved for ${file.path}, skipping`); continue; } - const needsUpdate = await this.shouldUpdateFile(localPath, content); - if (needsUpdate) { - await this.fileSystem.writeFileContent(localPath, content); - itemsUpdated++; - this.logger.debug(`Updated file: ${localPath}`); - } else { - this.logger.debug(`File unchanged: ${localPath}`); - } - - + await this.fileSystem.writeFileContent(localPath, content); + itemsUpdated++; } catch (error) { this.logger.warn(`Failed to sync file ${file.path}: ${error}`); // Continue with other files even if one fails From e3f68ab3d00ae35a6e9bf91ea5baf052abbeec34 Mon Sep 17 00:00:00 2001 From: "nathan.bougie" Date: Wed, 17 Sep 2025 20:37:26 -0400 Subject: [PATCH 5/5] feat: Reformat --- src/syncManager.ts | 39 +++++++++++++-------------------------- 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/src/syncManager.ts b/src/syncManager.ts index 1300390..221ec27 100644 --- a/src/syncManager.ts +++ b/src/syncManager.ts @@ -182,19 +182,19 @@ export class SyncManager { const fileName = this.fileSystem.getBasename(file.path); const localPath = this.fileSystem.joinPath(promptsDir, fileName); - const fileExists = await this.fileSystem.fileExists(localPath); - if(fileExists) { - const localFileContent = await this.fileSystem.readFileContent(localPath); - const localFileHash = await this.fileSystem.calculateFileHash(localFileContent); - - const githubFileHash = await this.github.getFileHash(owner, repo, file.path, this.config.branch); - if(localFileHash === githubFileHash) { - this.logger.info(`File ${file.path} is up to date, skipping`); - continue; - } - } - try { + const fileExists = await this.fileSystem.fileExists(localPath); + if(fileExists) { + const localFileContent = await this.fileSystem.readFileContent(localPath); + const localFileHash = await this.fileSystem.calculateFileHash(localFileContent); + + const githubFileHash = await this.github.getFileHash(owner, repo, file.path, this.config.branch); + if(localFileHash === githubFileHash) { + this.logger.info(`File ${file.path} is up to date, skipping`); + continue; + } + } + content = await this.github.getFileContent(owner, repo, file.path, this.config.branch); } catch (error) { // An error occured will retrieving file content, Return here @@ -208,7 +208,7 @@ export class SyncManager { this.logger.warn(`No content retrieved for ${file.path}, skipping`); continue; } - + await this.fileSystem.writeFileContent(localPath, content); itemsUpdated++; } catch (error) { @@ -291,19 +291,6 @@ export class SyncManager { }; } - private async shouldUpdateFile(localPath: string, newContent: string): Promise { - try { - if (!(await this.fileSystem.fileExists(localPath))) { - return true; // File doesn't exist, needs to be created - } - - const existingContent = await this.fileSystem.readFileContent(localPath); - return existingContent !== newContent; - } catch { - return true; // Error reading file, assume it needs updating - } - } - private scheduleNextSync(): void { // Clear existing timer if (this.timer) {