@@ -34,26 +34,130 @@ if (!supabaseUrl || !supabaseKey) {
3434
3535const supabase = createClient ( supabaseUrl , supabaseKey ) ;
3636const sqlite = new Database ( SQLITE_PATH , { readonly : true } ) ;
37+ const SYNC_SOURCE_TYPE = process . env . SYNC_SOURCE_TYPE || 'nightly_full' ;
38+ const MAX_SYNC_ERROR_CHARS = 2000 ;
3739
3840async function main ( ) {
39- console . log ( '=== Seeding Supabase from SQLite ===' ) ;
40- console . log ( `SQLite: ${ SQLITE_PATH } ` ) ;
41- console . log ( `Supabase: ${ supabaseUrl } ` ) ;
42- console . log ( '' ) ;
43-
44- await seedDetections ( ) ;
45- await seedDetectionTechniques ( ) ;
46- await seedTechniqueTactics ( ) ;
47- await seedAttackTechniques ( ) ;
48- await seedAttackActors ( ) ;
49- await seedAttackSoftware ( ) ;
50- await seedActorTechniques ( ) ;
51- await seedSoftwareTechniques ( ) ;
52- await seedProcedureReference ( ) ;
53- await seedStories ( ) ;
54-
55- console . log ( '\n=== Seed complete! ===' ) ;
56- sqlite . close ( ) ;
41+ let syncRunId : string | null = null ;
42+ let preRunDetections = 0 ;
43+
44+ try {
45+ console . log ( '=== Seeding Supabase from SQLite ===' ) ;
46+ console . log ( `SQLite: ${ SQLITE_PATH } ` ) ;
47+ console . log ( `Supabase: ${ supabaseUrl } ` ) ;
48+ console . log ( `Sync source: ${ SYNC_SOURCE_TYPE } ` ) ;
49+ console . log ( '' ) ;
50+
51+ preRunDetections = await getDetectionCount ( ) ;
52+ syncRunId = await startSyncRun ( preRunDetections ) ;
53+
54+ await seedDetections ( ) ;
55+ await seedDetectionTechniques ( ) ;
56+ await seedTechniqueTactics ( ) ;
57+ await seedAttackTechniques ( ) ;
58+ await seedAttackActors ( ) ;
59+ await seedAttackSoftware ( ) ;
60+ await seedActorTechniques ( ) ;
61+ await seedSoftwareTechniques ( ) ;
62+ await seedProcedureReference ( ) ;
63+ await seedStories ( ) ;
64+
65+ const postRunDetections = await getDetectionCount ( ) ;
66+ const detectionsAdded = Math . max ( postRunDetections - preRunDetections , 0 ) ;
67+
68+ await completeSyncRun ( syncRunId , {
69+ detectionsTotal : postRunDetections ,
70+ detectionsAdded,
71+ detectionsUpdated : 0 ,
72+ } ) ;
73+
74+ console . log ( '\n=== Seed complete! ===' ) ;
75+ } catch ( err ) {
76+ if ( syncRunId ) {
77+ await failSyncRun ( syncRunId , formatSyncError ( err ) ) ;
78+ }
79+ throw err ;
80+ } finally {
81+ sqlite . close ( ) ;
82+ }
83+ }
84+
85+ async function getDetectionCount ( ) : Promise < number > {
86+ const { count, error } = await supabase
87+ . from ( 'detections' )
88+ . select ( 'id' , { count : 'exact' , head : true } ) ;
89+
90+ if ( error || count === null ) {
91+ throw new Error ( `Failed to query detection count: ${ error ?. message || 'count unavailable' } ` ) ;
92+ }
93+
94+ return count ;
95+ }
96+
97+ async function startSyncRun ( preRunDetections : number ) : Promise < string > {
98+ const { data, error } = await supabase
99+ . from ( 'sync_runs' )
100+ . insert ( {
101+ source_type : SYNC_SOURCE_TYPE ,
102+ started_at : new Date ( ) . toISOString ( ) ,
103+ detections_total : preRunDetections ,
104+ detections_added : 0 ,
105+ detections_updated : 0 ,
106+ status : 'running' ,
107+ error : null ,
108+ } )
109+ . select ( 'id' )
110+ . single ( ) ;
111+
112+ if ( error || ! data ?. id ) {
113+ throw new Error ( `Failed to create sync run: ${ error ?. message || 'missing sync run id' } ` ) ;
114+ }
115+
116+ console . log ( `Started sync run: ${ data . id } ` ) ;
117+ return data . id as string ;
118+ }
119+
120+ async function completeSyncRun (
121+ syncRunId : string ,
122+ metrics : { detectionsTotal : number ; detectionsAdded : number ; detectionsUpdated : number }
123+ ) : Promise < void > {
124+ const { error } = await supabase
125+ . from ( 'sync_runs' )
126+ . update ( {
127+ completed_at : new Date ( ) . toISOString ( ) ,
128+ detections_total : metrics . detectionsTotal ,
129+ detections_added : metrics . detectionsAdded ,
130+ detections_updated : metrics . detectionsUpdated ,
131+ status : 'completed' ,
132+ error : null ,
133+ } )
134+ . eq ( 'id' , syncRunId ) ;
135+
136+ if ( error ) {
137+ throw new Error ( `Failed to finalize sync run ${ syncRunId } : ${ error . message } ` ) ;
138+ }
139+ }
140+
141+ async function failSyncRun ( syncRunId : string , errorMessage : string ) : Promise < void > {
142+ const { error } = await supabase
143+ . from ( 'sync_runs' )
144+ . update ( {
145+ completed_at : new Date ( ) . toISOString ( ) ,
146+ status : 'failed' ,
147+ error : errorMessage ,
148+ } )
149+ . eq ( 'id' , syncRunId ) ;
150+
151+ if ( error ) {
152+ console . error ( `Failed to mark sync run ${ syncRunId } as failed: ${ error . message } ` ) ;
153+ }
154+ }
155+
156+ function formatSyncError ( err : unknown ) : string {
157+ const message = err instanceof Error
158+ ? `${ err . message } ${ err . stack ? `\n${ err . stack } ` : '' } `
159+ : String ( err ) ;
160+ return message . slice ( 0 , MAX_SYNC_ERROR_CHARS ) ;
57161}
58162
59163async function upsertBatch ( table : string , rows : Record < string , unknown > [ ] , batchSize = 500 ) {
0 commit comments