1- import { writeFile , mkdir , readdir , readFile } from 'fs/promises' ;
1+ import { writeFile , mkdir , readdir , readFile , unlink } from 'fs/promises' ;
22import { join } from 'path' ;
33import { env } from '../../env.js' ;
44import type { Script , ScriptCard , GitHubFile } from '../../types/script' ;
@@ -158,7 +158,7 @@ export class GitHubJsonService {
158158 /**
159159 * Sync JSON files from a specific repository
160160 */
161- async syncJsonFilesForRepo ( repoUrl : string ) : Promise < { success : boolean ; message : string ; count : number ; syncedFiles : string [ ] } > {
161+ async syncJsonFilesForRepo ( repoUrl : string ) : Promise < { success : boolean ; message : string ; count : number ; syncedFiles : string [ ] ; deletedFiles : string [ ] } > {
162162 try {
163163 console . log ( `Starting JSON sync from repository: ${ repoUrl } ` ) ;
164164
@@ -170,44 +170,62 @@ export class GitHubJsonService {
170170 const localFiles = await this . getLocalJsonFiles ( ) ;
171171 console . log ( `Found ${ localFiles . length } local JSON files` ) ;
172172
173+ // Delete local JSON files that belong to this repo but are no longer in the remote
174+ const remoteFilenames = new Set ( githubFiles . map ( ( f ) => f . name ) ) ;
175+ const deletedFiles = await this . deleteLocalFilesRemovedFromRepo ( repoUrl , remoteFilenames ) ;
176+ if ( deletedFiles . length > 0 ) {
177+ console . log ( `Removed ${ deletedFiles . length } obsolete JSON file(s) no longer in ${ repoUrl } ` ) ;
178+ }
179+
173180 // Compare and find files that need syncing
174181 // For multi-repo support, we need to check if file exists AND if it's from this repo
175182 const filesToSync = await this . findFilesToSyncForRepo ( repoUrl , githubFiles , localFiles ) ;
176183 console . log ( `Found ${ filesToSync . length } files that need syncing from ${ repoUrl } ` ) ;
177184
178185 if ( filesToSync . length === 0 ) {
186+ const msg =
187+ deletedFiles . length > 0
188+ ? `All JSON files are up to date for repository: ${ repoUrl } . Removed ${ deletedFiles . length } obsolete file(s).`
189+ : `All JSON files are up to date for repository: ${ repoUrl } ` ;
179190 return {
180191 success : true ,
181- message : `All JSON files are up to date for repository: ${ repoUrl } ` ,
192+ message : msg ,
182193 count : 0 ,
183- syncedFiles : [ ]
194+ syncedFiles : [ ] ,
195+ deletedFiles
184196 } ;
185197 }
186198
187199 // Download and save only the files that need syncing
188200 const syncedFiles = await this . syncSpecificFiles ( repoUrl , filesToSync ) ;
189201
202+ const msg =
203+ deletedFiles . length > 0
204+ ? `Successfully synced ${ syncedFiles . length } JSON files from ${ repoUrl } , removed ${ deletedFiles . length } obsolete file(s).`
205+ : `Successfully synced ${ syncedFiles . length } JSON files from ${ repoUrl } ` ;
190206 return {
191207 success : true ,
192- message : `Successfully synced ${ syncedFiles . length } JSON files from ${ repoUrl } ` ,
208+ message : msg ,
193209 count : syncedFiles . length ,
194- syncedFiles
210+ syncedFiles,
211+ deletedFiles
195212 } ;
196213 } catch ( error ) {
197214 console . error ( `JSON sync failed for ${ repoUrl } :` , error ) ;
198215 return {
199216 success : false ,
200217 message : `Failed to sync JSON files from ${ repoUrl } : ${ error instanceof Error ? error . message : 'Unknown error' } ` ,
201218 count : 0 ,
202- syncedFiles : [ ]
219+ syncedFiles : [ ] ,
220+ deletedFiles : [ ]
203221 } ;
204222 }
205223 }
206224
207225 /**
208226 * Sync JSON files from all enabled repositories (main repo has priority)
209227 */
210- async syncJsonFiles ( ) : Promise < { success : boolean ; message : string ; count : number ; syncedFiles : string [ ] } > {
228+ async syncJsonFiles ( ) : Promise < { success : boolean ; message : string ; count : number ; syncedFiles : string [ ] ; deletedFiles : string [ ] } > {
211229 try {
212230 console . log ( 'Starting multi-repository JSON sync...' ) ;
213231
@@ -218,13 +236,15 @@ export class GitHubJsonService {
218236 success : false ,
219237 message : 'No enabled repositories found' ,
220238 count : 0 ,
221- syncedFiles : [ ]
239+ syncedFiles : [ ] ,
240+ deletedFiles : [ ]
222241 } ;
223242 }
224243
225244 console . log ( `Found ${ enabledRepos . length } enabled repositories` ) ;
226245
227246 const allSyncedFiles : string [ ] = [ ] ;
247+ const allDeletedFiles : string [ ] = [ ] ;
228248 const processedSlugs = new Set < string > ( ) ; // Track slugs we've already processed
229249 let totalSynced = 0 ;
230250
@@ -236,6 +256,7 @@ export class GitHubJsonService {
236256 const result = await this . syncJsonFilesForRepo ( repo . url ) ;
237257
238258 if ( result . success ) {
259+ allDeletedFiles . push ( ...( result . deletedFiles ?? [ ] ) ) ;
239260 // Only count files that weren't already processed from a higher priority repo
240261 const newFiles = result . syncedFiles . filter ( file => {
241262 const slug = file . replace ( '.json' , '' ) ;
@@ -259,19 +280,25 @@ export class GitHubJsonService {
259280 // Also update existing files that don't have repository_url set (backward compatibility)
260281 await this . updateExistingFilesWithRepositoryUrl ( ) ;
261282
283+ const msg =
284+ allDeletedFiles . length > 0
285+ ? `Successfully synced ${ totalSynced } JSON files from ${ enabledRepos . length } repositories, removed ${ allDeletedFiles . length } obsolete file(s).`
286+ : `Successfully synced ${ totalSynced } JSON files from ${ enabledRepos . length } repositories` ;
262287 return {
263288 success : true ,
264- message : `Successfully synced ${ totalSynced } JSON files from ${ enabledRepos . length } repositories` ,
289+ message : msg ,
265290 count : totalSynced ,
266- syncedFiles : allSyncedFiles
291+ syncedFiles : allSyncedFiles ,
292+ deletedFiles : allDeletedFiles
267293 } ;
268294 } catch ( error ) {
269295 console . error ( 'Multi-repository JSON sync failed:' , error ) ;
270296 return {
271297 success : false ,
272298 message : `Failed to sync JSON files: ${ error instanceof Error ? error . message : 'Unknown error' } ` ,
273299 count : 0 ,
274- syncedFiles : [ ]
300+ syncedFiles : [ ] ,
301+ deletedFiles : [ ]
275302 } ;
276303 }
277304 }
@@ -316,6 +343,36 @@ export class GitHubJsonService {
316343 }
317344 }
318345
346+ /**
347+ * Delete local JSON files that belong to this repo but are no longer in the remote list.
348+ * Returns the list of deleted filenames.
349+ */
350+ private async deleteLocalFilesRemovedFromRepo ( repoUrl : string , remoteFilenames : Set < string > ) : Promise < string [ ] > {
351+ this . initializeConfig ( ) ;
352+ const localFiles = await this . getLocalJsonFiles ( ) ;
353+ const deletedFiles : string [ ] = [ ] ;
354+
355+ for ( const file of localFiles ) {
356+ try {
357+ const filePath = join ( this . localJsonDirectory ! , file ) ;
358+ const content = await readFile ( filePath , 'utf-8' ) ;
359+ const script = JSON . parse ( content ) as Script ;
360+
361+ if ( script . repository_url === repoUrl && ! remoteFilenames . has ( file ) ) {
362+ await unlink ( filePath ) ;
363+ const slug = file . replace ( / \. j s o n $ / , '' ) ;
364+ this . scriptCache . delete ( slug ) ;
365+ deletedFiles . push ( file ) ;
366+ console . log ( `Removed obsolete script JSON: ${ file } (no longer in ${ repoUrl } )` ) ;
367+ }
368+ } catch {
369+ // If we can't read or parse the file, skip (do not delete)
370+ }
371+ }
372+
373+ return deletedFiles ;
374+ }
375+
319376 /**
320377 * Find files that need syncing for a specific repository
321378 * This checks if file exists locally AND if it's from the same repository
0 commit comments