@@ -2,133 +2,41 @@ import type { Result } from 'nimiq-validator-trustscore/types'
22import type { ValidatorJSON } from './schemas'
33import { readdir , readFile } from 'node:fs/promises'
44import { extname } from 'node:path'
5- import process from 'node:process'
6- import { consola } from 'consola'
7- import { $fetch } from 'ofetch'
85import { join } from 'pathe'
96import { validatorSchema } from './schemas'
7+
108/**
119 * Import validators from a folder containing .json files.
12- *
13- * This function is expected to be used when initializing the database with validators, so it will throw
14- * an error if the files are not valid and the program should stop.
10+ * Used by the validation script to check files on disk.
1511 */
16- export async function importValidatorsFromFiles ( folderPath : string ) : Result < any [ ] > {
12+ export async function importValidators ( nimiqNetwork : string ) : Result < ValidatorJSON [ ] > {
13+ if ( ! nimiqNetwork )
14+ return [ false , 'Nimiq network is required' , undefined ]
15+
16+ const folderPath = `public/validators/${ nimiqNetwork } `
1717 const allFiles = await readdir ( folderPath )
1818 const files = allFiles
1919 . filter ( f => extname ( f ) === '.json' )
2020 . filter ( f => ! f . endsWith ( '.example.json' ) )
2121
22- const rawValidators : any [ ] = [ ]
22+ const validators : ValidatorJSON [ ] = [ ]
2323 for ( const file of files ) {
2424 const filePath = join ( folderPath , file )
2525 const fileContent = await readFile ( filePath , 'utf8' )
2626
27+ let raw : unknown
2728 try {
28- rawValidators . push ( JSON . parse ( fileContent ) )
29+ raw = JSON . parse ( fileContent )
2930 }
3031 catch ( error ) {
3132 return [ false , `Invalid JSON in file: ${ file } . Error: ${ error } ` , undefined ]
3233 }
33- }
34- return [ true , undefined , rawValidators ]
35- }
36-
37- /**
38- * Import validators from GitHub using the official GitHub REST API.
39- */
40- async function importValidatorsFromGitHub ( path : string , { gitBranch } : Pick < ImportValidatorsFromFilesOptions , 'gitBranch' > ) : Result < any [ ] > {
41- // Default to main branch if not specified
42- const branch = gitBranch || 'main'
43-
44- // Check if running in a fork PR (GitHub Actions sets GITHUB_HEAD_REPOSITORY)
45- const headRepo = process . env . GITHUB_HEAD_REPOSITORY // format: "owner/repo"
46- const baseRepo = process . env . GITHUB_REPOSITORY || 'nimiq/validators-api'
47-
48- // Use head repo if it's different from base (fork PR scenario)
49- const [ owner , repo ] = ( headRepo && headRepo !== baseRepo ? headRepo : baseRepo ) . split ( '/' )
50-
51- const apiUrl = `https://api.github.com/repos/${ owner } /${ repo } /contents/${ path } ?ref=${ branch } `
52-
53- // 1. List directory contents
54- let listing : Array < {
55- name : string
56- path : string
57- type : 'file' | 'dir'
58- download_url : string
59- } >
60-
61- const headers : HeadersInit = { 'User-Agent' : 'request' }
62- try {
63- listing = await $fetch ( apiUrl , { headers } )
64- }
65- catch ( e ) {
66- consola . warn ( `Error listing validators folder on GitHub: ${ e } ` )
67- return [ false , `Could not list validators on GitHub ${ apiUrl } | ${ e } ` , undefined ]
68- }
69-
70- // 2. Filter only .json files (skip .example.json)
71- const jsonFiles = listing . filter ( file =>
72- file . type === 'file'
73- && file . name . endsWith ( '.json' )
74- && ! file . name . endsWith ( '.example.json' ) ,
75- )
76-
77- // 3. Fetch each file’s raw contents
78- const rawContents = await Promise . all ( jsonFiles . map ( async ( file ) => {
79- try {
80- return await $fetch < string > ( file . download_url , { headers } )
81- }
82- catch ( e ) {
83- consola . warn ( `Failed to download ${ file . path } : ${ e } ` )
84- return [ false , `Failed to download ${ file . path } : ${ e } ` , undefined ]
85- }
86- } ) )
87-
88- // 4. Parse JSON and return
89- const parsed = rawContents . filter ( ( c ) : c is string => Boolean ( c ) ) . map ( c => JSON . parse ( c ! ) )
90-
91- return [ true , undefined , parsed ]
92- }
93-
94- interface ImportValidatorsFromFilesOptions {
95- nimiqNetwork ?: string
96- gitBranch ?: string
97- shouldStore ?: boolean
98- }
9934
100- /**
101- * Import validators from either the filesystem or GitHub, then validate & store.
102- */
103- export async function importValidators ( source : 'filesystem' | 'github' , options : ImportValidatorsFromFilesOptions = { } ) : Result < ValidatorJSON [ ] > {
104- const { nimiqNetwork, shouldStore = true , gitBranch } = options
105- if ( ! nimiqNetwork )
106- return [ false , 'Nimiq network is required' , undefined ]
107-
108- const path = `public/validators/${ nimiqNetwork } `
109-
110- const [ ok , readError , data ] = source === 'filesystem'
111- ? await importValidatorsFromFiles ( path )
112- : await importValidatorsFromGitHub ( path , { gitBranch } )
113-
114- if ( ! ok )
115- return [ false , readError , undefined ]
116-
117- const validators : ValidatorJSON [ ] = [ ]
118- for ( const validator of data ) {
119- const { data, success, error } = validatorSchema . safeParse ( validator )
120- if ( ! success )
121- return [ false , `Invalid validator ${ validator . name } (${ validator . address } ) data: ${ error || 'Unknown error' } . ${ JSON . stringify ( { path, gitBranch, source } ) } ` , undefined ]
122- validators . push ( data )
35+ const parsed = validatorSchema . safeParse ( raw )
36+ if ( ! parsed . success )
37+ return [ false , `Invalid validator ${ file } : ${ parsed . error } ` , undefined ]
38+ validators . push ( parsed . data )
12339 }
12440
125- if ( ! shouldStore )
126- return [ true , undefined , validators ]
127-
128- const results = await Promise . allSettled ( validators . map ( v => storeValidator ( v . address , v , { upsert : true } ) ) )
129- const failures = results . filter ( r => r . status === 'rejected' )
130- if ( failures . length > 0 )
131- return [ false , `Errors importing validators: ${ failures . map ( ( f : any ) => f . reason ) . join ( ', ' ) } ` , undefined ]
132-
13341 return [ true , undefined , validators ]
13442}
0 commit comments